diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..76e0df0a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,65 @@ +name: ๐Ÿ› Bug Report +description: Report an issue with course content, broken examples, or incorrect instructions +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thanks for helping improve this course! Please fill out the details below so we can fix the issue quickly. + + - type: dropdown + id: chapter + attributes: + label: Which chapter is affected? + options: + - "00 - Quick Start" + - "01 - First Steps" + - "02 - Context and Conversations" + - "03 - Development Workflows" + - "04 - Create Specialized AI Assistants" + - "05 - Automate Repetitive Tasks" + - "06 - Connect to GitHub, Databases & APIs" + - "07 - Putting It All Together" + - "Sample code (samples/)" + - "Appendices" + - "Other" + validations: + required: true + + - type: textarea + id: description + attributes: + label: What happened? + description: Describe the issue clearly. If a command or code example didn't work, include the exact text you copied. + placeholder: "When I ran the command from the 'Code Review' section, I got an error..." + validations: + required: true + + - type: textarea + id: expected + attributes: + label: What did you expect to happen? + placeholder: "I expected to see the code review output shown in the example..." + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment + description: Your OS, terminal, Copilot CLI version, and Python version + placeholder: | + - OS: macOS 15 / Windows 11 / Ubuntu 24.04 + - Terminal: iTerm2 / Windows Terminal / default + - Copilot CLI version: (run `copilot --version`) + - Python version: (run `python3 --version`) + validations: + required: false + + - type: textarea + id: screenshots + attributes: + label: Screenshots or terminal output + description: If applicable, paste terminal output or attach screenshots + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..07cc61be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,48 @@ +name: ๐Ÿ’ก Feature Request +description: Suggest new content, a new chapter topic, or an improvement to the course +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Have an idea to make this course better? We'd love to hear it! + + - type: dropdown + id: type + attributes: + label: What kind of improvement? + options: + - "New chapter or section" + - "New sample app or exercise" + - "Improve existing content" + - "Add a new agent or skill example" + - "Better explanation of a concept" + - "Other" + validations: + required: true + + - type: textarea + id: description + attributes: + label: Describe your idea + description: What would you like to see added or changed? Why would it help learners? + placeholder: "It would be helpful to have a section on..." + validations: + required: true + + - type: textarea + id: audience + attributes: + label: Who would benefit? + description: Is this for complete beginners, intermediate users, or a specific audience? + placeholder: "Beginners who are new to the terminal..." + validations: + required: false + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Have you seen this covered elsewhere? Is there a workaround? + validations: + required: false diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 16600736..839c4c3a 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -1,5 +1,15 @@ { "entries": { + "actions/checkout@v6.0.2": { + "repo": "actions/checkout", + "version": "v6.0.2", + "sha": "de0fac2e4500dabe0009e67214ff5f5447ce83dd" + }, + "actions/download-artifact@v8.0.1": { + "repo": "actions/download-artifact", + "version": "v8.0.1", + "sha": "3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c" + }, "actions/github-script@v8": { "repo": "actions/github-script", "version": "v8", @@ -10,6 +20,11 @@ "version": "v9", "sha": "373c709c69115d41ff229c7e5df9f8788daa9553" }, + "actions/upload-artifact@v7": { + "repo": "actions/upload-artifact", + "version": "v7", + "sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" + }, "github/gh-aw-actions/setup@v0.68.1": { "repo": "github/gh-aw-actions/setup", "version": "v0.68.1", diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..5ed64e51 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,70 @@ +# Copilot Instructions + +These instructions guide GitHub Copilot when working in this repository. + +## Project Context + +This is a **beginner-friendly educational course** teaching GitHub Copilot CLI. The repo contains Markdown chapters (00โ€“07), Python/C#/JavaScript sample apps, and supporting assets (images, demo GIFs, glossary). It is **not** a software product โ€” it is technical courseware. + +## Writing Conventions + +- **Audience**: Beginners with no AI/ML experience. Explain every technical term on first use. +- **Tone**: Friendly, encouraging, practical. Avoid jargon without explanation. +- **Examples**: All code blocks and `copilot` commands must be copy-paste ready. Test them mentally before including. +- **Naming**: Use kebab-case for session names, file names, and identifiers (e.g., `book-app-review`, not `book app review`). +- **Command syntax**: Standardize flag format โ€” use `--flag=value` consistently when a value is required, `--flag` when boolean. +- **Precision**: Don't over-specify tool behavior that may vary across shells or OS. Describe what the user will see, not implementation details. +- **Fallbacks**: When referencing tool version requirements (e.g., `gh` CLI version), always include upgrade instructions or a manual alternative. + +## Content Conventions (from PR review patterns) + +These patterns were mined from actual PR review feedback and represent recurring maintainer expectations: + +- When showing multi-step workflows, ensure all prerequisite steps are included (e.g., `git add` before `git diff --staged`). +- When introducing a concept with an example, use consistent naming throughout the section โ€” don't mix kebab-case and quoted names. +- When describing a command's behavior, match the level of specificity in the official release notes โ€” don't state behavior that may differ across environments. +- If a feature requires a minimum tool version, mention the version AND provide a fallback path for users who can't upgrade yet. + +## Sample Code Conventions + +- **Primary sample**: Always use `samples/book-app-project/` (Python) for examples in chapters. +- **Test framework**: pytest โ€” test files go in `samples/book-app-project/tests/` and follow `test_*.py` naming. +- **Python version**: 3.10+ (per `samples/book-app-project/pyproject.toml`). +- **Intentional bugs**: Files in `samples/book-app-buggy/` and `samples/buggy-code/` contain **deliberate bugs** for exercises. Never fix them. + +## Chapter Structure + +Every chapter (00โ€“07) follows the same pattern in its `README.md`: + +1. Real-World Analogy +2. Core Concepts +3. Hands-On Examples +4. Assignment +5. What's Next + +Do not deviate from this structure when editing or adding chapter content. + +## Markdown Formatting + +- Use standard GitHub-Flavored Markdown. +- Images go in the repo-root `assets/` directory. +- Use relative links for cross-chapter references (e.g., `../03-development-workflows/README.md`). +- Emoji usage is encouraged for section headers (matching existing style). + +## Maintenance Matrix + +| Change Made | Files to Update | +|---|---| +| New chapter added | `README.md` (course table), `AGENTS.md` (structure table), `assets/learning-path.png` | +| Chapter content updated | The chapter's `README.md`, verify cross-references in adjacent chapters | +| New sample app variant added | `AGENTS.md` (structure table), `samples/` directory, relevant chapter references | +| Sample app code changed | `samples/book-app-project/tests/` (update/add tests), chapters referencing that code | +| Bug intentionally added to buggy samples | `samples/book-app-buggy/` or `samples/buggy-code/` only โ€” do NOT update tests | +| New skill added | `.github/skills/{skill-name}/SKILL.md`, `samples/skills/` (example copy), Chapter 05 | +| New agent template added | `samples/agents/`, Chapter 04 | +| New MCP config added | `samples/mcp-configs/`, Chapter 06 | +| Glossary term introduced | `GLOSSARY.md` โ€” add definition in alphabetical order | +| npm scripts changed | `package.json`, `AGENTS.md` (build section) | +| Devcontainer updated | `.devcontainer/devcontainer.json`, Chapter 00 (setup instructions) | +| Image or banner changed | `assets/` directory, any README referencing the image | +| Copilot CLI version requirements change | Chapter 00, Chapter 01, `.devcontainer/devcontainer.json` | 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 new file mode 100644 index 00000000..f55e358b --- /dev/null +++ b/.github/scripts/copy-content-engine-files.js @@ -0,0 +1,353 @@ +#!/usr/bin/env node +/** + * Copy the course content subset required by the CSE content engine. + * + * 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 { + cpSync, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} = require('fs'); +const { dirname, join, relative, resolve } = require('path'); + +const sourceRoot = process.cwd(); +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#', + type: 'object', + properties: { + aliases: { + type: 'array', + description: 'Relative paths to redirect to this item', + items: { + type: 'string', + description: 'A relative path to redirect to this item', + }, + }, + audience: { + type: 'string', + description: 'The intended audience for the guide', + }, + description: { + type: 'string', + description: 'A brief description of the item', + }, + icon: { + type: 'string', + description: 'An icon to represent the item', + }, + id: { + type: 'string', + description: 'A unique identifier for the guide', + }, + params: { + type: 'object', + description: "Flexible parameters that don't affect presentation", + }, + slug: { + type: 'string', + description: 'A kebab-case identifier', + }, + title: { + type: 'string', + description: 'The display name of the item', + }, + weight: { + type: 'integer', + description: 'The order to display the item in', + }, + }, + required: ['title', 'description', 'weight'], + additionalProperties: true, +}; + +function log(message) { + console.log(` ${message}`); +} + +function fail(message) { + console.error(`\nError: ${message}`); + process.exit(1); +} + +function ensureSafeDestination() { + if (!existsSync(destinationParent)) { + fail(`Destination parent does not exist: ${destinationParent}`); + } + + const resolvedSource = resolve(sourceRoot); + const resolvedDestination = resolve(destinationRoot); + + if (resolvedSource === resolvedDestination) { + fail('Destination cannot be the source repository root.'); + } + + if (resolvedDestination.startsWith(`${resolvedSource}/`)) { + fail('Destination cannot be inside the source repository.'); + } +} + +function resetDestination() { + rmSync(destinationRoot, { recursive: true, force: true }); + log(`Cleared ${destinationRoot}`); +} + +function copyFile(sourcePath, destinationPath) { + mkdirSync(dirname(destinationPath), { recursive: true }); + + if (sourcePath.endsWith('.md')) { + writeFileSync(destinationPath, prepareMarkdownForContentEngine(sourcePath), 'utf8'); + } else { + cpSync(sourcePath, destinationPath); + } + + log(`Copied ${relative(sourceRoot, sourcePath)} -> ${relative(destinationRoot, destinationPath)}`); +} + +function prepareMarkdownForContentEngine(sourcePath) { + const markdown = readFileSync(sourcePath, 'utf8'); + const frontmatter = getMarkdownFrontmatter(markdown); + + if (!frontmatter) { + return markdown; + } + + return markdown.replace(/^\r?\n*/, `---\n${frontmatter}\n---\n\n`); +} + +function getMarkdownFrontmatter(markdown) { + const hiddenFrontmatter = markdown.match(/^/)?.[1]; + const visibleFrontmatter = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1]; + + return (hiddenFrontmatter ?? visibleFrontmatter)?.replace(/\r\n/g, '\n'); +} + +function getFrontmatterField(frontmatter, field) { + return frontmatter.match(new RegExp(`^${field}:.*$`, 'm'))?.[0]; +} + +function writeIndexFromReadme(sourceReadmePath, destinationDirectory, extraFields = []) { + const markdown = readFileSync(sourceReadmePath, 'utf8'); + const frontmatter = getMarkdownFrontmatter(markdown); + + if (!frontmatter) { + fail(`Cannot create index.yml because ${relative(sourceRoot, sourceReadmePath)} has no frontmatter.`); + } + + const indexFields = ['title', 'description', 'slug', 'weight', 'icon'] + .map((field) => getFrontmatterField(frontmatter, field)) + .filter(Boolean); + indexFields.push(...extraFields); + + if (indexFields.length === 0) { + fail(`Cannot create index.yml because ${relative(sourceRoot, sourceReadmePath)} has no index metadata.`); + } + + mkdirSync(destinationDirectory, { recursive: true }); + writeFileSync(join(destinationDirectory, 'index.yml'), `${indexFields.join('\n')}\n`, 'utf8'); + log(`Generated ${relative(destinationRoot, join(destinationDirectory, 'index.yml'))}`); +} + +function writeContentEngineSchema() { + const destinationPath = join(destinationRoot, 'schema.json'); + writeFileSync(destinationPath, `${JSON.stringify(contentEngineSchema, null, 2)}\n`, 'utf8'); + log(`Generated ${relative(destinationRoot, destinationPath)}`); +} + +function copyDirectory(sourcePath, destinationPath) { + if (!existsSync(sourcePath)) { + fail(`Required directory does not exist: ${relative(sourceRoot, sourcePath)}`); + } + + cpSync(sourcePath, destinationPath, { recursive: true }); + log(`Copied ${relative(sourceRoot, sourcePath)}/ -> ${relative(destinationRoot, destinationPath)}/`); +} + +function getChapterFolders() { + return readdirSync(sourceRoot) + .filter((entry) => /^0[0-7]-/.test(entry)) + .filter((entry) => statSync(join(sourceRoot, entry)).isDirectory()) + .sort(); +} + +function stripFragmentAndQuery(target) { + return target.split('#')[0].split('?')[0]; +} + +function isExternalLink(target) { + return /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith('//') || target.startsWith('#'); +} + +function getChapterLocalMarkdownLinks(chapterPath) { + const readmePath = join(chapterPath, 'README.md'); + const readme = readFileSync(readmePath, 'utf8'); + const links = new Set(); + const patterns = [ + /\[[^\]]+\]\(([^)\s]+\.md(?:#[^)]+)?)(?:\s+"[^"]*")?\)/gi, + /]*\bhref=["']([^"']+\.md(?:#[^"']+)?)["']/gi, + ]; + + for (const pattern of patterns) { + for (const match of readme.matchAll(pattern)) { + const target = stripFragmentAndQuery(match[1]); + if (!target || isExternalLink(target)) { + continue; + } + + const resolvedTarget = resolve(chapterPath, target); + if (dirname(resolvedTarget) === resolve(chapterPath) && resolvedTarget !== resolve(readmePath)) { + links.add(resolvedTarget); + } + } + } + + return [...links].sort(); +} + +function copyAppendices() { + const sourceAppendices = join(sourceRoot, 'appendices'); + const destinationAppendices = join(destinationRoot, 'appendices'); + + if (!existsSync(sourceAppendices)) { + fail('Required appendices directory does not exist.'); + } + + writeIndexFromReadme(join(sourceAppendices, 'README.md'), destinationAppendices); + + for (const markdownFile of findMarkdownFiles(sourceAppendices)) { + copyFile(markdownFile, join(destinationAppendices, relative(sourceAppendices, markdownFile))); + } +} + +function copyCourseContent() { + console.log(`Overlaying course content into:\n${destinationRoot}\n`); + + mkdirSync(destinationRoot, { recursive: true }); + + copyFile(join(sourceRoot, 'README.md'), join(destinationRoot, 'README.md')); + writeContentEngineSchema(); + writeIndexFromReadme(join(sourceRoot, 'README.md'), destinationRoot, ['icon: CopilotIcon']); + copyDirectory(join(sourceRoot, 'assets'), join(destinationRoot, 'assets')); + + for (const chapterFolder of getChapterFolders()) { + const sourceChapter = join(sourceRoot, chapterFolder); + const destinationChapter = join(destinationRoot, chapterFolder); + + mkdirSync(destinationChapter, { recursive: true }); + copyFile(join(sourceChapter, 'README.md'), join(destinationChapter, 'README.md')); + writeIndexFromReadme(join(sourceChapter, 'README.md'), destinationChapter); + copyDirectory(join(sourceChapter, 'assets'), join(destinationChapter, 'assets')); + + for (const linkedMarkdown of getChapterLocalMarkdownLinks(sourceChapter)) { + copyFile(linkedMarkdown, join(destinationChapter, relative(sourceChapter, linkedMarkdown))); + } + } + + copyAppendices(); +} + +function findMarkdownFiles(directory) { + const files = []; + + for (const entry of readdirSync(directory)) { + const path = join(directory, entry); + const stat = statSync(path); + + if (stat.isDirectory()) { + files.push(...findMarkdownFiles(path)); + } else if (entry.endsWith('.md')) { + files.push(path); + } + } + + return files; +} + +function validateMarkdownImagePaths() { + const imagePatterns = [ + /!\[[^\]]*]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, + /]*\bsrc=["']([^"']+)["']/gi, + ]; + const brokenLinks = []; + + for (const markdownFile of findMarkdownFiles(destinationRoot)) { + const markdown = readFileSync(markdownFile, 'utf8'); + + for (const pattern of imagePatterns) { + for (const match of markdown.matchAll(pattern)) { + const target = stripFragmentAndQuery(match[1]); + if (!target || isExternalLink(target)) { + continue; + } + + const resolvedTarget = target.startsWith('/') + ? join(destinationRoot, target.slice(1)) + : resolve(dirname(markdownFile), target); + + if (!existsSync(resolvedTarget)) { + const line = markdown.slice(0, match.index).split('\n').length; + brokenLinks.push(`${relative(destinationRoot, markdownFile)}:${line} -> ${target}`); + } + } + } + } + + if (brokenLinks.length > 0) { + fail(`Broken copied Markdown image references:\n${brokenLinks.join('\n')}`); + } + + console.log('\nValidation passed: all copied Markdown image references resolve.'); +} + +function validateMarkdownFrontmatter() { + const requiredFields = contentEngineSchema.required ?? []; + const missingFrontmatter = []; + + for (const markdownFile of findMarkdownFiles(destinationRoot)) { + const markdown = readFileSync(markdownFile, 'utf8'); + const frontmatter = markdown.match(/^---\n([\s\S]*?)\n---\n/)?.[1]; + const relativePath = relative(destinationRoot, markdownFile); + + if (!frontmatter) { + missingFrontmatter.push(`${relativePath}: missing frontmatter`); + continue; + } + + const missingFields = requiredFields.filter( + (field) => !new RegExp(`^${field}:`, 'm').test(frontmatter), + ); + + if (missingFields.length > 0) { + missingFrontmatter.push(`${relativePath}: missing ${missingFields.join(', ')}`); + } + } + + if (missingFrontmatter.length > 0) { + fail(`Copied Markdown frontmatter does not match schema requirements:\n${missingFrontmatter.join('\n')}`); + } + + console.log('Validation passed: copied Markdown frontmatter includes required schema fields.'); +} + +ensureSafeDestination(); +resetDestination(); +copyCourseContent(); +validateMarkdownFrontmatter(); +validateMarkdownImagePaths(); +console.log('\nDone.'); diff --git a/.github/scripts/create-tapes.js b/.github/scripts/create-tapes.js index 66583962..95e0f69e 100644 --- a/.github/scripts/create-tapes.js +++ b/.github/scripts/create-tapes.js @@ -136,13 +136,13 @@ console.log('๐Ÿ“ Creating tape files from demos.json...\n'); let created = 0; for (const demo of config.demos) { - const imagesDir = join(rootDir, demo.chapter, 'images'); - const tapePath = join(imagesDir, `${demo.name}.tape`); + const assetsDir = join(rootDir, demo.chapter, 'assets'); + const tapePath = join(assetsDir, `${demo.name}.tape`); - // Ensure images directory exists - if (!existsSync(imagesDir)) { - mkdirSync(imagesDir, { recursive: true }); - console.log(` Created: ${demo.chapter}/images/`); + // Ensure assets directory exists + if (!existsSync(assetsDir)) { + mkdirSync(assetsDir, { recursive: true }); + console.log(` Created: ${demo.chapter}/assets/`); } // Generate tape content @@ -150,7 +150,7 @@ for (const demo of config.demos) { // Write tape file writeFileSync(tapePath, content); - console.log(` โœ“ ${demo.chapter}/images/${demo.name}.tape`); + console.log(` โœ“ ${demo.chapter}/assets/${demo.name}.tape`); created++; } 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/scripts/fix-translated-markdown.js b/.github/scripts/fix-translated-markdown.js new file mode 100644 index 00000000..34952f24 --- /dev/null +++ b/.github/scripts/fix-translated-markdown.js @@ -0,0 +1,396 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const path = require("path"); + +const repoRoot = process.cwd(); +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/fix-translated-markdown.js "); + process.exit(1); +} + +const tableHeadersByLanguage = { + es: "| Capรญtulo | Tรญtulo | Lo que construirรกs |", + ko: "| ์žฅ | ์ œ๋ชฉ | ๋งŒ๋“ค ๋‚ด์šฉ |", + ja: "| ็ซ  | ใ‚ฟใ‚คใƒˆใƒซ | ไฝœๆˆใ™ใ‚‹ใ‚‚ใฎ |", + "zh-CN": "| ็ซ ่Š‚ | ๆ ‡้ข˜ | ไฝ ๅฐ†ๆž„ๅปบ็š„ๅ†…ๅฎน |", +}; + +const allFiles = new Set(walkFiles(repoRoot).map(toPosixRelative)); +const errors = []; +let changedFiles = 0; + +for (const language of languages) { + const translationRoot = path.join(repoRoot, "translations", language); + + if (!fs.existsSync(translationRoot)) { + console.log(`No translations found for ${language}; skipping.`); + continue; + } + + const markdownFiles = walkFiles(translationRoot).filter((file) => file.endsWith(".md")); + + for (const filePath of markdownFiles) { + const original = fs.readFileSync(filePath, "utf8"); + const fixed = fixMarkdown(original, filePath, language); + + if (fixed !== original) { + fs.writeFileSync(filePath, fixed); + changedFiles += 1; + console.log(`Fixed translated Markdown: ${toPosixRelative(filePath)}`); + } + + validateMarkdown(fixed, filePath, language); + } +} + +if (errors.length > 0) { + console.error("\nTranslated Markdown validation failed:"); + for (const error of errors) { + console.error(`- ${error}`); + } + process.exit(1); +} + +console.log(`Translated Markdown cleanup complete. Files changed: ${changedFiles}`); + +function fixMarkdown(content, filePath, language) { + let fixed = fixKnownTableHeaders(content, language); + const headingSlugs = getHeadingSlugs(fixed); + const headingSlugList = getHeadingSlugList(fixed); + const sourceFile = getSourceFileForTranslation(filePath, language); + + fixed = fixed.replace(/\[([^\]\n]+)\]\((#[^)]+)\)/g, (match, label, destination) => { + const currentSlug = destination.slice(1); + + if (headingSlugs.has(currentSlug)) { + return match; + } + + const labelSlug = slugifyHeading(label); + + if (headingSlugs.has(labelSlug)) { + return `[${label}](#${labelSlug})`; + } + + const candidates = [...headingSlugs].filter( + (slug) => slug.startsWith(`${labelSlug}-`) || labelSlug.startsWith(`${slug}-`), + ); + + if (candidates.length === 1) { + return `[${label}](#${candidates[0]})`; + } + + const sourceHeadingIndex = getSourceHeadingIndex(sourceFile, currentSlug); + + if (sourceHeadingIndex !== -1 && sourceHeadingIndex < headingSlugList.length) { + return `[${label}](#${headingSlugList[sourceHeadingIndex]})`; + } + + return match; + }); + + return fixed.replace(/(\]\()([^)]+)(\))/g, (_match, prefix, destination, suffix) => { + return `${prefix}${fixDestination(destination, filePath, language)}${suffix}`; + }); +} + +function fixKnownTableHeaders(content, language) { + const translatedHeader = tableHeadersByLanguage[language]; + + if (!translatedHeader) { + return content; + } + + return content.replace( + /^\|\s*Chapter\s*\|\s*Title\s*\|\s*What You'll Build\s*\|$/gm, + translatedHeader, + ); +} + +function fixDestination(destination, filePath, language) { + if (isExternal(destination) || destination.startsWith("#") || destination.startsWith("<")) { + return destination; + } + + const { target, fragment } = splitDestination(destination); + + if (!target || target.startsWith("#")) { + return destination; + } + + const translatedDir = path.posix.dirname(toPosixRelative(filePath)); + const currentTarget = normalizePosix(path.posix.join(translatedDir, target)); + const translatedRoot = `translations/${language}`; + const sourceRelativeFile = path.posix.relative(translatedRoot, toPosixRelative(filePath)); + const sourceDir = path.posix.dirname(sourceRelativeFile); + const sourceTarget = normalizePosix(path.posix.join(sourceDir, target)); + + if (pathExists(currentTarget)) { + return `${target}${fixCrossFileFragment(fragment, sourceTarget, currentTarget)}`; + } + + if (!pathExists(sourceTarget)) { + return destination; + } + + const translatedTarget = normalizePosix(path.posix.join(translatedRoot, sourceTarget)); + const preferredTarget = pathExists(translatedTarget) ? translatedTarget : sourceTarget; + const relativeTarget = path.posix.relative(translatedDir, preferredTarget) || "."; + const normalizedTarget = relativeTarget.startsWith(".") ? relativeTarget : `./${relativeTarget}`; + + return `${normalizedTarget}${fixCrossFileFragment(fragment, sourceTarget, preferredTarget)}`; +} + +function validateMarkdown(content, filePath, language) { + const fileRelative = toPosixRelative(filePath); + const fileDir = path.posix.dirname(fileRelative); + const headingSlugs = getHeadingSlugs(content); + const sourceFile = getSourceFileForTranslation(filePath, language); + + for (const destination of getDestinations(content)) { + if (isExternal(destination) || destination.startsWith("<")) { + continue; + } + + const { target, fragment } = splitDestination(destination); + + if (!target) { + const slug = fragment.slice(1); + + if ( + slug && + !headingSlugs.has(slug) && + getSourceHeadingIndex(sourceFile, slug) !== -1 + ) { + errors.push(`${fileRelative} links to missing heading #${slug}`); + } + + continue; + } + + const resolvedTarget = normalizePosix(path.posix.join(fileDir, target)); + + if (!pathExists(resolvedTarget)) { + const sourceTarget = getSourceTargetForDestination(filePath, language, target); + + if (sourceTarget && !pathExists(sourceTarget)) { + continue; + } + + errors.push(`${fileRelative} links to missing file ${destination}`); + continue; + } + + // Cross-file heading anchors are best-effort fixed above by mapping source + // heading order to translated heading order. Avoid failing on anchors that + // come from generated HTML IDs or pre-existing source content. + } +} + +function getDestinations(content) { + const destinations = []; + const pattern = /\]\(([^)]+)\)/g; + let match; + + while ((match = pattern.exec(content)) !== null) { + destinations.push(match[1]); + } + + return destinations; +} + +function getHeadingSlugs(content) { + return new Set(getHeadingSlugList(content)); +} + +function getHeadingSlugList(content) { + return getHeadingSlugListWith(content, slugifyHeading); +} + +function getSourceHeadingIndex(sourceFile, slug) { + if (!sourceFile || !pathExists(sourceFile)) { + return -1; + } + + const sourceContent = fs.readFileSync(path.join(repoRoot, sourceFile), "utf8"); + const slugLists = [ + getHeadingSlugListWith(sourceContent, slugifyHeading), + getHeadingSlugListWith(sourceContent, slugifyGitHubHeading), + ]; + + for (const slugs of slugLists) { + const index = slugs.indexOf(slug); + + if (index !== -1) { + return index; + } + } + + return -1; +} + +function getHeadingSlugListWith(content, slugifier) { + const slugs = new Map(); + const result = []; + + for (const line of content.split(/\r?\n/)) { + const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line); + + if (!match) { + continue; + } + + const baseSlug = slugifier(match[2]); + const count = slugs.get(baseSlug) || 0; + const slug = count === 0 ? baseSlug : `${baseSlug}-${count}`; + slugs.set(baseSlug, count + 1); + result.push(slug); + } + + return result; +} + +function fixCrossFileFragment(fragment, sourceTarget, translatedTarget) { + if (!fragment || !sourceTarget.endsWith(".md") || !translatedTarget.endsWith(".md")) { + return fragment; + } + + if (!pathExists(sourceTarget) || !pathExists(translatedTarget)) { + return fragment; + } + + const currentSlug = fragment.slice(1); + const translatedContent = fs.readFileSync(path.join(repoRoot, translatedTarget), "utf8"); + const translatedSlugs = getHeadingSlugList(translatedContent); + + if (translatedSlugs.includes(currentSlug)) { + return fragment; + } + + const sourceContent = fs.readFileSync(path.join(repoRoot, sourceTarget), "utf8"); + const sourceSlugs = getHeadingSlugList(sourceContent); + const sourceIndex = sourceSlugs.indexOf(currentSlug); + + if (sourceIndex === -1 || sourceIndex >= translatedSlugs.length) { + return fragment; + } + + return `#${translatedSlugs[sourceIndex]}`; +} + +function getSourceFileForTranslation(filePath, language) { + const translatedRoot = `translations/${language}`; + const sourceFile = path.posix.relative(translatedRoot, toPosixRelative(filePath)); + + if (sourceFile.startsWith("..")) { + return ""; + } + + return sourceFile; +} + +function getSourceTargetForDestination(filePath, language, target) { + const sourceFile = getSourceFileForTranslation(filePath, language); + + if (!sourceFile) { + return ""; + } + + const sourceDir = path.posix.dirname(sourceFile); + return normalizePosix(path.posix.join(sourceDir, target)); +} + +function slugifyHeading(value) { + return value + .replace(/<[^>]+>/g, "") + .replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1") + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") + .replace(/[`*_~]/g, "") + .trim() + .toLocaleLowerCase() + .replace(/[^\p{Letter}\p{Number}\p{Mark}\s-]/gu, "") + .trim() + .replace(/\s+/g, "-"); +} + +function slugifyGitHubHeading(value) { + return value + .replace(/<[^>]+>/g, "") + .replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1") + .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1") + .replace(/[`*_~]/g, "") + .trim() + .toLocaleLowerCase() + .replace(/[^\p{Letter}\p{Number}\p{Mark}\s-]/gu, "") + .replace(/\s/g, "-"); +} + +function splitDestination(destination) { + const hashIndex = destination.indexOf("#"); + + if (hashIndex === -1) { + return { target: destination, fragment: "" }; + } + + return { + target: destination.slice(0, hashIndex), + fragment: destination.slice(hashIndex), + }; +} + +function isExternal(destination) { + return /^(https?:|mailto:|tel:)/i.test(destination); +} + +function pathExists(relativePath) { + if (allFiles.has(relativePath)) { + return true; + } + + if (fs.existsSync(path.join(repoRoot, relativePath))) { + return true; + } + + const indexPath = normalizePosix(path.posix.join(relativePath, "README.md")); + return allFiles.has(indexPath); +} + +function walkFiles(directory) { + if (!fs.existsSync(directory)) { + return []; + } + + const entries = fs.readdirSync(directory, { withFileTypes: true }); + const files = []; + + for (const entry of entries) { + if (entry.name === ".git" || entry.name === "node_modules") { + continue; + } + + const entryPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + files.push(...walkFiles(entryPath)); + } else if (entry.isFile()) { + files.push(entryPath); + } + } + + return files; +} + +function toPosixRelative(filePath) { + return normalizePosix(path.relative(repoRoot, filePath)); +} + +function normalizePosix(value) { + return value.split(path.sep).join(path.posix.sep).replace(/\\/g, "/"); +} diff --git a/.github/scripts/generate-chapter-headers.py b/.github/scripts/generate-chapter-headers.py index 5c44a927..0520f6be 100644 --- a/.github/scripts/generate-chapter-headers.py +++ b/.github/scripts/generate-chapter-headers.py @@ -23,7 +23,7 @@ # Get project root (parent of scripts folder) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR)) -BACKGROUND_IMAGE = os.path.join(PROJECT_ROOT, "images", "chapter-header-bg.png") +BACKGROUND_IMAGE = os.path.join(PROJECT_ROOT, "assets", "chapter-header-bg.png") # Font settings - 25% larger than original 36px FONT_SIZE = 45 @@ -112,8 +112,8 @@ def generate_header(chapter_folder, title, font): y = (height - text_height) // 2 draw.text((x, y), title, fill=(255, 255, 255), font=font) - # Save to chapter's images folder - output_dir = os.path.join(PROJECT_ROOT, chapter_folder, "images") + # Save to chapter's assets folder + output_dir = os.path.join(PROJECT_ROOT, chapter_folder, "assets") os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, "chapter-header.png") diff --git a/.github/scripts/generate-demos.js b/.github/scripts/generate-demos.js index 09076750..457ef558 100644 --- a/.github/scripts/generate-demos.js +++ b/.github/scripts/generate-demos.js @@ -2,7 +2,7 @@ /** * Generate course demo GIFs from .tape files * - * This script finds all .tape files in [chapter]/images/ folders and runs VHS + * This script finds all .tape files in [chapter]/assets/ folders and runs VHS * to generate GIFs. VHS is run from the project root so that @file references * in prompts resolve correctly. * @@ -135,7 +135,7 @@ function cleanupCopilotWrapper() { try { rmSync(wrapperDir, { recursive: true }); } catch (e) { /* ignore */ } } -// Find all .tape files in [chapter]/images/ folders +// Find all .tape files in [chapter]/assets/ folders function findTapeFiles(dir, chapterFilter) { const tapeFiles = []; @@ -151,17 +151,17 @@ function findTapeFiles(dir, chapterFilter) { if (!matches) continue; } - const imagesDir = join(fullPath, 'images'); - if (existsSync(imagesDir)) { + const assetsDir = join(fullPath, 'assets'); + if (existsSync(assetsDir)) { try { - const imagesEntries = readdirSync(imagesDir); - for (const file of imagesEntries) { + const assetsEntries = readdirSync(assetsDir); + for (const file of assetsEntries) { if (file.endsWith('.tape')) { - tapeFiles.push(join(imagesDir, file)); + tapeFiles.push(join(assetsDir, file)); } } } catch (e) { - // Can't read images folder, skip + // Can't read assets folder, skip } } } diff --git a/.github/scripts/preview-gifs.js b/.github/scripts/preview-gifs.js index f3fec522..0c095d1e 100644 --- a/.github/scripts/preview-gifs.js +++ b/.github/scripts/preview-gifs.js @@ -32,11 +32,11 @@ function findGifs() { const gifs = []; for (const entry of readdirSync(rootDir)) { if (!/^\d{2}-/.test(entry)) continue; - const imagesDir = join(rootDir, entry, 'images'); - if (!existsSync(imagesDir)) continue; - for (const file of readdirSync(imagesDir)) { + const assetsDir = join(rootDir, entry, 'assets'); + if (!existsSync(assetsDir)) continue; + for (const file of readdirSync(assetsDir)) { if (file.endsWith('-demo.gif')) { - gifs.push({ path: join(imagesDir, file), chapter: entry }); + gifs.push({ path: join(assetsDir, file), chapter: entry }); } } } diff --git a/.github/scripts/verify-gifs.js b/.github/scripts/verify-gifs.js index 93cd1c17..04c94aa0 100644 --- a/.github/scripts/verify-gifs.js +++ b/.github/scripts/verify-gifs.js @@ -41,11 +41,11 @@ function findGifs(dir) { const fullPath = join(dir, entry); const stat = statSync(fullPath); if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') { - const imagesDir = join(fullPath, 'images'); - if (existsSync(imagesDir)) { - for (const file of readdirSync(imagesDir)) { + const assetsDir = join(fullPath, 'assets'); + if (existsSync(assetsDir)) { + for (const file of readdirSync(assetsDir)) { if (file.endsWith('-demo.gif')) { - gifs.push(join(imagesDir, file)); + gifs.push(join(assetsDir, file)); } } } diff --git a/.github/uvs.csv b/.github/uvs.csv index 88e23ad1..345702fd 100644 --- a/.github/uvs.csv +++ b/.github/uvs.csv @@ -32,3 +32,108 @@ "04/03",4110 "04/04",4751 "04/05",2716 +"04/06",2941 +"04/07",2861 +"04/08",2415 +"04/09",2479 +"04/10",2128 +"04/11",1016 +"04/12",907 +"04/13",1823 +"04/14",1957 +"04/15",2144 +"04/16",1864 +"04/17",1818 +"04/18",772 +"04/19",769 +"04/20",1661 +"04/21",1835 +"04/22",1791 +"04/23",1547 +"04/24",1584 +"04/25",624 +"04/26",695 +"04/27",1277 +"04/28",1155 +"04/29",1196 +"04/30",1191 +"05/01",738 +"05/02",529 +"05/03",571 +"05/04",1171 +"05/05",982 +"05/06",934 +"05/07",996 +"05/08",1034 +"05/09",455 +"05/10",481 +"05/11",923 +"05/12",728 +"05/13",816 +"05/14",803 +"05/15",754 +"05/16",464 +"05/17",440 +"05/18",840 +"05/19",744 +"05/20",791 +"05/21",806 +"05/22",834 +"05/23",373 +"05/24",418 +"05/25",801 +"05/26",778 +"05/27",907 +"05/28",775 +"05/29",731 +"05/30",329 +"05/31",352 +"06/01",681 +"06/02",521 +"06/03",613 +"06/04",516 +"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 eeaaec8e..4f566887 100644 --- a/.github/views.csv +++ b/.github/views.csv @@ -31,3 +31,108 @@ "04/03",8218 "04/04",9957 "04/05",5559 +"04/06",6377 +"04/07",6169 +"04/08",5478 +"04/09",5775 +"04/10",4931 +"04/11",2340 +"04/12",2239 +"04/13",4430 +"04/14",4371 +"04/15",4911 +"04/16",4418 +"04/17",4574 +"04/18",1904 +"04/19",2011 +"04/20",3775 +"04/21",4253 +"04/22",4086 +"04/23",3523 +"04/24",3727 +"04/25",1451 +"04/26",1663 +"04/27",2641 +"04/28",2282 +"04/29",2634 +"04/30",2869 +"05/01",2013 +"05/02",1317 +"05/03",1336 +"05/04",2124 +"05/05",2018 +"05/06",1906 +"05/07",2121 +"05/08",2393 +"05/09",1097 +"05/10",1003 +"05/11",1832 +"05/12",1461 +"05/13",1710 +"05/14",1721 +"05/15",1538 +"05/16",1138 +"05/17",1093 +"05/18",1751 +"05/19",1447 +"05/20",1494 +"05/21",1654 +"05/22",1823 +"05/23",1013 +"05/24",1111 +"05/25",1902 +"05/26",1634 +"05/27",1980 +"05/28",1565 +"05/29",1534 +"05/30",840 +"05/31",892 +"06/01",1430 +"06/02",1007 +"06/03",1271 +"06/04",1065 +"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 new file mode 100644 index 00000000..a06a9c38 --- /dev/null +++ b/.github/workflows/co-op-translator.yml @@ -0,0 +1,126 @@ +name: Co-op Translator + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "**/*.md" + - "**/*.png" + - "**/*.jpg" + - "**/*.jpeg" + - "**/*.webp" + - "!translations/**" + - "!translated_images/**" + - "!.github/**" + - "!samples/**" + +permissions: + contents: write + pull-requests: write + +jobs: + translate-content: + name: Translate Markdown and Images + runs-on: ubuntu-latest + timeout-minutes: 90 + + env: + TRANSLATION_LANGUAGES: "es" + PYTHONIOENCODING: utf-8 + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} + AZURE_OPENAI_MODEL_NAME: ${{ secrets.AZURE_OPENAI_MODEL_NAME }} + AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }} + AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }} + AZURE_AI_SERVICE_API_KEY: ${{ secrets.AZURE_AI_SERVICE_API_KEY }} + AZURE_AI_SERVICE_ENDPOINT: ${{ secrets.AZURE_AI_SERVICE_ENDPOINT }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_ORG_ID: ${{ secrets.OPENAI_ORG_ID }} + OPENAI_CHAT_MODEL_ID: ${{ secrets.OPENAI_CHAT_MODEL_ID }} + OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install Co-op Translator + run: | + python -m pip install --upgrade pip + python -m pip install co-op-translator + + - name: Translate Markdown + run: | + for attempt in 1 2 3; do + if translate -l "$TRANSLATION_LANGUAGES" -md -y -s --repo-url "https://github.com/github/copilot-cli-for-beginners.git"; then + exit 0 + fi + echo "Markdown translation attempt $attempt failed." + if [ "$attempt" -lt 3 ]; then + sleep 30 + fi + done + exit 1 + + - name: Translate images + run: | + for attempt in 1 2 3; do + if translate -l "$TRANSLATION_LANGUAGES" -img -y -s --repo-url "https://github.com/github/copilot-cli-for-beginners.git"; then + exit 0 + fi + echo "Image translation attempt $attempt failed." + if [ "$attempt" -lt 3 ]; then + sleep 30 + fi + done + exit 1 + + - name: Normalize and review translations + run: | + migrate-links -l "$TRANSLATION_LANGUAGES" -y + node .github/scripts/fix-translated-markdown.js "$TRANSLATION_LANGUAGES" + 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" \ + "translated_images/$language/.github" \ + "translated_images/$language/samples" + done + + - name: Upload Co-op Translator logs + if: always() + 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@v8 + with: + token: ${{ secrets.GH_AW_GITHUB_TOKEN }} + commit-message: "Update translations via Co-op Translator" + title: "Update translations via Co-op Translator" + body: | + This PR updates Markdown and image translations generated by Co-op Translator. + + Generated content is available in the `translations/` and `translated_images/` directories. + branch: update-translations + base: main + labels: translation, automated-pr + delete-branch: true + add-paths: | + translations/ + translated_images/ diff --git a/.github/workflows/course-updater.lock.yml b/.github/workflows/course-updater.lock.yml index b3ed1201..72d7a585 100644 --- a/.github/workflows/course-updater.lock.yml +++ b/.github/workflows/course-updater.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"392faada2ff3018cb510c08e4ccc127f642791025fab2c2965f4b8c100b17539","compiler_version":"v0.68.1","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"7b67212e2086070d4622e72ea2514453d5a2ba0093a2ba686ecb4ba55a530f59","compiler_version":"v0.68.1","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc","version":"v0.68.1"}]} # ___ _ _ # / _ \ | | (_) @@ -22,7 +22,7 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Daily check for new GitHub Copilot CLI features and updates. Opens a PR if the course content needs updating. +# Weekly check (Mondays) for new GitHub Copilot CLI features and updates. Opens a PR if the course content needs updating. # # Secrets used: # - COPILOT_GITHUB_TOKEN @@ -42,8 +42,8 @@ name: "Course Updater" "on": schedule: - - cron: "38 11 * * *" - # Friendly format: daily (scattered) + - cron: "38 11 * * 1" + # Friendly format: weekly on monday (scattered) workflow_dispatch: inputs: aw_context: @@ -158,19 +158,19 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_96aea1fc2c8492ca_EOF' + cat << 'GH_AW_PROMPT_da18aaba3a78fe8f_EOF' - GH_AW_PROMPT_96aea1fc2c8492ca_EOF + GH_AW_PROMPT_da18aaba3a78fe8f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_96aea1fc2c8492ca_EOF' + cat << 'GH_AW_PROMPT_da18aaba3a78fe8f_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_96aea1fc2c8492ca_EOF + GH_AW_PROMPT_da18aaba3a78fe8f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_96aea1fc2c8492ca_EOF' + cat << 'GH_AW_PROMPT_da18aaba3a78fe8f_EOF' The following GitHub context information is available for this workflow: @@ -200,12 +200,12 @@ jobs: {{/if}} - GH_AW_PROMPT_96aea1fc2c8492ca_EOF + GH_AW_PROMPT_da18aaba3a78fe8f_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_96aea1fc2c8492ca_EOF' + cat << 'GH_AW_PROMPT_da18aaba3a78fe8f_EOF' {{#runtime-import .github/workflows/course-updater.md}} - GH_AW_PROMPT_96aea1fc2c8492ca_EOF + GH_AW_PROMPT_da18aaba3a78fe8f_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 @@ -369,9 +369,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_40c7340e920eba23_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8267a15d6774039b_EOF' {"create_pull_request":{"base_branch":"main","labels":["automated-update","copilot-cli-updates"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[bot] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_40c7340e920eba23_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_8267a15d6774039b_EOF - name: Write Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -569,7 +569,7 @@ jobs: export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_8cd7d8bd3e51245c_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + cat << GH_AW_MCP_CONFIG_1fee4f046759e415_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -610,7 +610,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_8cd7d8bd3e51245c_EOF + GH_AW_MCP_CONFIG_1fee4f046759e415_EOF - name: Download activation artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -1031,7 +1031,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 env: WORKFLOW_NAME: "Course Updater" - WORKFLOW_DESCRIPTION: "Daily check for new GitHub Copilot CLI features and updates. Opens a PR if the course content needs updating." + WORKFLOW_DESCRIPTION: "Weekly check (Mondays) for new GitHub Copilot CLI features and updates. Opens a PR if the course content needs updating." HAS_PATCH: ${{ needs.agent.outputs.has_patch }} with: script: | diff --git a/.github/workflows/course-updater.md b/.github/workflows/course-updater.md index 80b61eea..ef418b82 100644 --- a/.github/workflows/course-updater.md +++ b/.github/workflows/course-updater.md @@ -1,8 +1,8 @@ --- name: "Course Updater" -description: "Daily check for new GitHub Copilot CLI features and updates. Opens a PR if the course content needs updating." +description: "Weekly check (Mondays) for new GitHub Copilot CLI features and updates. Opens a PR if the course content needs updating." on: - schedule: daily + schedule: weekly on monday workflow_dispatch: tools: bash: ["curl", "gh"] @@ -37,11 +37,15 @@ Look for: - Significant changes to existing features (renames, deprecations) - New customization options (e.g. instructions, agents, skills, MCP, plugins) -## Step 2 โ€” Compare against the current course content and existing PRs +## Step 2 โ€” Check for existing open PRs to avoid duplicates + +Before doing any content comparison, list all open pull requests in this repo that have the `automated-update` or `copilot-cli-updates` labels. Read their titles and descriptions to understand which features or changes each PR already covers. Build a list of features that are **already addressed** by existing PRs โ€” you must exclude those features from any updates you propose later. If every feature you found in Step 1 is already covered by an open PR, stop here and report that no new updates are needed. + +## Step 3 โ€” Compare against the current course content This course targets beginners, so only include content changes that cater to that audience. For example, if a new feature is advanced, marked as experimental, or otherwise doesn't qualify as a "beginner" level feature, don't include it in the course content since we don't want to overwhelm learners. Determine what is most relevant and helpful for beginners learning about the Copilot CLI. -Read all of the readme files in the repo and compare the features documented there against what you found in Step 1. Also check existing pull requests to see if any updates are already in progress. DO NOT duplicate efforts if an update has already been proposed in an open PR. +Read all of the readme files in the repo and compare the features documented there against what you found in Step 1. Identify: - **Missing features** โ€” new capabilities not yet documented @@ -49,13 +53,13 @@ Identify: If there is nothing new or everything is already up to date, stop here and report that no updates are needed. -## Step 3 โ€” Update the course content +## Step 4 โ€” Update the course content If updates are needed, make a decision on which chapter(s) need to be updated. If the new information can be added to existing chapter(s), edit those chapters to include refinements, new sections, or updated information as needed. Remember that this course targets beginners, so ensure that any new content is explained clearly and simply, with examples if possible. -## Step 4 โ€” Open a pull request +## Step 5 โ€” Open a pull request Create a pull request with your changes, using the `main` branch as the base branch. The PR title should summarize what was updated (e.g., "Add /plan command documentation"). The PR body should list: 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.lock.yml b/.github/workflows/translation-polisher.lock.yml new file mode 100644 index 00000000..bdc752b1 --- /dev/null +++ b/.github/workflows/translation-polisher.lock.yml @@ -0,0 +1,1327 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"cbc9f85ce905d99f4478b7d93d8191a4d9b0d24e13640ae360044e3b41ed5a9e","compiler_version":"v0.68.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc","version":"v0.68.1"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.68.1). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Reviews Co-op Translator pull requests and polishes generated translations without changing source content. +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_CI_TRIGGER_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + +name: "Translation Polisher" +"on": + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + workflow_dispatch: + inputs: + aw_context: + default: "" + description: Agent caller context (used internally by Agentic Workflows). + required: false + type: string + workflow_run: + # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation + branches: + - main + types: + - completed + workflows: + - Co-op Translator + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}" + cancel-in-progress: true + +run-name: "Translation Polisher" + +jobs: + activation: + needs: pre_activation + # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation + if: > + (needs.pre_activation.outputs.activated == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id)) && + (github.event_name != 'workflow_run' || github.event.workflow_run.repository.id == github.repository_id && + (!(github.event.workflow_run.repository.fork))) + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: "" + comment_repo: "" + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} + GH_AW_INFO_VERSION: "1.0.21" + GH_AW_INFO_AGENT_VERSION: "1.0.21" + GH_AW_INFO_CLI_VERSION: "v0.68.1" + GH_AW_INFO_WORKFLOW_NAME: "Translation Polisher" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.18" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_WORKFLOW_FILE: "translation-polisher.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_COMPILED_VERSION: "v0.68.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_007b898c8d34eb6e_EOF' + + GH_AW_PROMPT_007b898c8d34eb6e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_007b898c8d34eb6e_EOF' + + Tools: update_pull_request, add_labels, push_to_pull_request_branch, missing_tool, missing_data, noop + GH_AW_PROMPT_007b898c8d34eb6e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" + cat << 'GH_AW_PROMPT_007b898c8d34eb6e_EOF' + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + - **checkouts**: The following repositories have been checked out and are available in the workspace: + - `$GITHUB_WORKSPACE` โ†’ `__GH_AW_GITHUB_REPOSITORY__` (cwd) [full history, all branches available as remote-tracking refs] [additional refs fetched: *] + - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). + + + GH_AW_PROMPT_007b898c8d34eb6e_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_007b898c8d34eb6e_EOF' + + {{#runtime-import .github/workflows/translation-polisher.md}} + GH_AW_PROMPT_007b898c8d34eb6e_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: translationpolisher + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Set runtime paths + id: set-runtime-paths + run: | + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_OUTPUT" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_OUTPUT" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + - name: Fetch additional refs + env: + GH_AW_FETCH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + header=$(printf "x-access-token:%s" "${GH_AW_FETCH_TOKEN}" | base64 -w 0) + git -c "http.extraheader=Authorization: Basic ${header}" fetch origin '+refs/heads/*:refs/remotes/origin/*' + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + - name: Write Safe Outputs Config + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_fa8fe511dd276b01_EOF + {"add_labels":{"allowed":["translation-polished"],"github-token":"${GH_AW_GITHUB_TOKEN}","max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_to_pull_request_branch":{"allowed_files":["translations/**/*.md","translations/**/.co-op-translator.json"],"github-token":"${GH_AW_GITHUB_TOKEN}","if_no_changes":"ignore","labels":["translation","automated-pr"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_files_policy":"allowed","protected_path_prefixes":[".github/",".agents/"],"target":"*"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":false,"github-token":"${GH_AW_GITHUB_TOKEN}","max":1,"target":"*"}} + GH_AW_SAFE_OUTPUTS_CONFIG_fa8fe511dd276b01_EOF + - name: Write Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"translation-polished\"].", + "push_to_pull_request_branch": " CONSTRAINTS: Maximum 1 push(es) can be made.", + "update_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be updated. Target: *." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "push_to_pull_request_branch": { + "defaultMax": 1, + "fields": { + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "pull_request_number": { + "issueOrPRNumber": true + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + }, + "update_pull_request": { + "defaultMax": 1, + "fields": { + "body": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "draft": { + "type": "boolean" + }, + "operation": { + "type": "string", + "enum": [ + "replace", + "append", + "prepend" + ] + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + }, + "customValidation": "requiresOneOf:title,body" + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} + GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_592a00f4d05d7e7b_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "repos,pull_requests" + }, + "guard-policies": { + "allow-only": { + "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", + "repos": "$GITHUB_MCP_GUARD_REPOS" + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_592a00f4d05d7e7b_EOF + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Clean git credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool github + # --allow-tool safeoutputs + # --allow-tool shell(cat) + # --allow-tool shell(date) + # --allow-tool shell(echo) + # --allow-tool shell(gh:*) + # --allow-tool shell(git add:*) + # --allow-tool shell(git branch:*) + # --allow-tool shell(git checkout:*) + # --allow-tool shell(git commit:*) + # --allow-tool shell(git merge:*) + # --allow-tool shell(git rm:*) + # --allow-tool shell(git status) + # --allow-tool shell(git switch:*) + # --allow-tool shell(git:*) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(ls) + # --allow-tool shell(node) + # --allow-tool shell(pwd) + # --allow-tool shell(sort) + # --allow-tool shell(tail) + # --allow-tool shell(uniq) + # --allow-tool shell(wc) + # --allow-tool shell(yq) + # --allow-tool write + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh:*)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(node)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.68.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect inference access error + id: detect-inference-error + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,localhost,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + if-no-files-found: ignore + - name: Upload firewall audit logs + if: always() + continue-on-error: true + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: firewall-audit-logs + path: | + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-translation-polisher" + cancel-in-progress: false + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Translation Polisher" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Translation Polisher" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Translation Polisher" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Translation Polisher" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "translation-polisher" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + WORKFLOW_NAME: "Translation Polisher" + WORKFLOW_DESCRIPTION: "Reviews Co-op Translator pull requests and polishes generated translations without changing source content." + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.68.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + + pre_activation: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/translation-polisher" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_WORKFLOW_ID: "translation-polisher" + GH_AW_WORKFLOW_NAME: "Translation Polisher" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + push_commit_sha: ${{ steps.process_safe_outputs.outputs.push_commit_sha }} + push_commit_url: ${{ steps.process_safe_outputs.outputs.push_commit_url }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@2fe53acc038ba01c3bbdc767d4b25df31ca5bdfc # v0.68.1 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Checkout repository + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 1 + - name: Configure Git credentials + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,localhost,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_labels\":{\"allowed\":[\"translation-polished\"],\"github-token\":\"${{ secrets.GH_AW_GITHUB_TOKEN }}\",\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"allowed_files\":[\"translations/**/*.md\",\"translations/**/.co-op-translator.json\"],\"github-token\":\"${{ secrets.GH_AW_GITHUB_TOKEN }}\",\"if_no_changes\":\"ignore\",\"labels\":[\"translation\",\"automated-pr\"],\"max\":1,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_files_policy\":\"allowed\",\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"target\":\"*\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"github-token\":\"${{ secrets.GH_AW_GITHUB_TOKEN }}\",\"max\":1,\"target\":\"*\"}}" + GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + with: + name: safe-outputs-items + path: /tmp/gh-aw/safe-output-items.jsonl + if-no-files-found: ignore + diff --git a/.github/workflows/translation-polisher.md b/.github/workflows/translation-polisher.md new file mode 100644 index 00000000..7eff0d74 --- /dev/null +++ b/.github/workflows/translation-polisher.md @@ -0,0 +1,270 @@ +--- +name: "Translation Polisher" +description: "Reviews Co-op Translator pull requests and polishes generated translations without changing source content." +on: + workflow_run: + workflows: ["Co-op Translator"] + types: [completed] + branches: [main] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: +permissions: + contents: read + pull-requests: read + issues: read +engine: copilot +tools: + bash: ["gh", "git", "node"] + edit: + github: + toolsets: [repos, pull_requests] +checkout: + fetch-depth: 0 + fetch: ["*"] +network: defaults +safe-outputs: + allowed-domains: + - github.com + noop: + report-as-issue: false + add-labels: + allowed: [translation-polished] + max: 1 + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN }} + update-pull-request: + target: "*" + title: false + body: true + max: 1 + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN }} + push-to-pull-request-branch: + target: "*" + labels: [translation, automated-pr] + protected-files: allowed + allowed-files: + - "translations/**/*.md" + - "translations/**/.co-op-translator.json" + if-no-changes: "ignore" + max: 1 + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN }} +--- + +# Polish Co-op Translator PRs + +You are a translation editor for the **GitHub Copilot CLI for Beginners** course. Your job is to review and polish Markdown files generated by Co-op Translator, while preserving the repository's translation structure and source content. + +## Scope + +Only work on Co-op Translator pull requests. + +A pull request is in scope when all of these are true: + +1. The pull request title starts with `Update translations via Co-op Translator`. +2. The pull request has both labels: `translation` and `automated-pr`. +3. The pull request changes files under `translations/`. + +If this workflow is triggered by `workflow_run` or `workflow_dispatch`, find the current open pull request with head branch `update-translations`. If no matching pull request exists, stop with a no-op. + +If this workflow is triggered by `pull_request`, inspect the triggering pull request. If it is not in scope, stop with a no-op. + +## Loop prevention + +Before editing, inspect the pull request's latest commit, current diff, labels, and body. + +Stop with a no-op only if all of these are true: + +1. The translated Markdown already satisfies the quality checklist below and no file changes are needed. +2. The pull request body already contains an up-to-date managed `## Translation Quality Review` section with one grade row for every changed translated Markdown file. +3. Every existing grade in the managed review section is **A- or higher**. + +If the latest commit appears to be from this Translation Polisher workflow but the pull request body is missing the `## Translation Quality Review` section, do not edit files. Still review and grade the changed translated Markdown files and update the pull request body. + +If the pull request body already contains a managed `## Translation Quality Review` section and any row is graded **B+ or lower**, treat those files as required repair targets. Review and polish those files again before deciding whether to push changes or report blocking issues in the managed pull request body section. + +Do not add churn. If the translation is already good enough and the PR body already has current A- or higher grades, leave it unchanged. + +## Files you may change + +You may edit only translated Markdown files: + +- `translations/**/*.md` + +Exclude these generated translation paths from review, grading, and edits: + +- `translations/*/.github/**` +- `translations/*/samples/**` + +Leave skill definition Markdown in English. + +Do not edit: + +- English source files +- `.co-op-translator.json` +- workflow files +- scripts +- sample source code outside translated Markdown + +## Required process + +> **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/**`. +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. +7. Run an untranslated learner-facing text pass: + - Check headings, visible table headers, navigation tables, list labels, callout/admonition labels, and human-facing link labels. + - Translate leftover English when it is learner-facing prose. + - Preserve product names, commands, file paths, branch names, package names, URLs, badge URLs, code identifiers, and GitHub UI labels that learners must recognize. +8. Run the deterministic cleanup script after edits: + + ```bash + node .github/scripts/fix-translated-markdown.js "" + ``` + +9. Perform a final review of each changed target-language file against its English source file. Grade each file using A, A-, B+, B, B-, C, D, or F. +10. Continue improving the translation until every changed target-language file earns **A- or higher**. +11. If any changed target-language file remains below A- after reasonable polishing, do not push changes and do not add the `translation-polished` label. Update the pull request body with `Translation status: Needs polish` and include the blocking issues and current file grades in the managed review section. +12. Review your final diff. If it contains anything outside `translations/**/*.md` or Co-op metadata files named `translations/**/.co-op-translator.json`, revert those changes. +13. Push your changes to the target pull request branch using the safe output only when every changed target-language file is A- or higher. +14. Update the pull request body with a final per-file grade table using the instructions in **Pull request body update**. +15. Add the `translation-polished` label only when every changed target-language file is A- or higher. +16. Do not add a pull request comment. The managed pull request body section is the source of truth for review status, grades, and notes. + +## Safe output limits + +Emit each safe output type at most once: + +- At most one `push_to_pull_request_branch`. +- At most one `update_pull_request`. +- At most one `add_labels`, and only when the `translation-polished` label is not already present on the pull request. +- Do not emit `add_comment`; comments are not an allowed safe output for this workflow. + +Do not emit duplicate branch push, pull request update, label, or comment requests. If a label is already present, do not emit an `add_labels` request for it. If you emit `add_labels`, include the target pull request number as `item_number`. Put the quality summary and polishing summary in the single managed pull request body section. + +## Quality checklist + +For every translated Markdown file you edit: + +- Preserve all code blocks exactly unless the original English text inside the code block is instructional prose that should intentionally be translated. +- Preserve command names, file paths, package names, product names, URLs, and badge URLs. +- Preserve Markdown tables, lists, blockquotes, headings, and admonitions. +- Preserve links and image destinations. Translate only the human-facing link label when appropriate. +- Translate human-facing prose naturally for the target language. +- Translate visible headings, table headings, navigation labels, list labels, callout labels, and link labels when they are human-facing content. +- Keep the beginner-friendly tone of the English source. +- Avoid literal phrasing that sounds unnatural in the target language. +- Do not remove the Co-op Translator disclaimer. +- Do not edit translation metadata. + +## Final translation review rubric + +Before pushing, review each changed translated Markdown file against its corresponding English source and assign a grade. + +Grade **A- or higher** only when all of these are true: + +- The translation preserves the meaning, scope, warnings, and calls to action from the English source. +- The Markdown structure, links, images, headings, tables, badges, and code blocks are intact. +- Human-facing prose, navigation labels, table headings, and link labels are translated where appropriate. +- Product names, commands, file paths, URLs, package names, and UI labels are preserved when they should be. +- The text sounds natural to a technical learner in the target language, not like a literal sentence-by-sentence translation. +- Terminology is consistent within the file and across the same target language. +- The tone remains beginner-friendly, practical, and encouraging. + +Use **B+ or lower** if any visible learner-facing text remains unnecessarily in English, if phrasing is noticeably awkward, if terminology is inconsistent, or if important nuance is missing. Keep polishing until every changed translated Markdown file is A- or higher. + +## Language quality profiles + +Apply the profile only when that language is present in the pull request. + +### Shared rules for all languages + +- Preserve product names such as **GitHub Copilot CLI**, **GitHub Codespaces**, and **Azure AI Foundry** unless an official localized name is clearly standard in the target-language ecosystem. +- Preserve commands, code, file paths, URLs, badge URLs, package names, branch names, and repository names. +- Translate human-facing link labels, table headings, navigation labels, and instructional prose. +- Keep English technical terms only when they are common in the target language, are official UI labels, or are product/feature names. +- Prefer natural beginner-friendly phrasing over literal translation. +- Use consistent terminology within each file and across the same language. +- Do not over-localize acronyms or terms that target-language developers normally use in English. + +### Spanish (`es`) + +- Use clear, neutral Spanish for a broad technical audience. +- Prefer natural active voice over passive constructions. +- Localize beginner-facing concepts such as issue and pull request when clarity improves, but keep GitHub UI terms in English when they refer to the UI label. +- Keep common technical acronyms such as API. +- Avoid overly literal phrasing. For example, prefer natural wording such as `potenciar`, `colega experto`, and `donde se encuentra cada una` when the sentence context calls for it. + +### Korean (`ko`) + +- Use polite, clear technical Korean appropriate for educational documentation. +- Keep product names in English unless there is a clear official Korean name. +- Prefer commonly used Korean developer terminology for concepts, but do not translate CLI commands, file paths, Git branch names, package names, or GitHub UI labels that learners must recognize. +- Avoid overly formal or machine-translated sentence endings; keep instructions direct and approachable. + +### Japanese (`ja`) + +- Use clear technical Japanese with a polite instructional tone. +- Keep product names in English unless there is a clear official Japanese name. +- Prefer standard Japanese developer terms and natural sentence structure. +- Avoid overly literal English word order. +- Do not translate commands, file paths, Git branch names, package names, or GitHub UI labels that learners must recognize. + +### Simplified Chinese (`zh-CN`) + +- Use Simplified Chinese. +- Use clear mainland Chinese technical documentation style. +- Keep product names in English unless there is a clear official Simplified Chinese name. +- Avoid Taiwan/Hong Kong traditional terminology. +- Do not translate commands, file paths, Git branch names, package names, or GitHub UI labels that learners must recognize. + +## Pull request body update + +After the final review, update the pull request body with a managed translation-quality section. Replace only the managed block between these exact lowercase markers: + +```markdown + + +``` + +The body must include exactly one managed block and exactly one section inside that block with this heading: + +```markdown +## Translation Quality Review +``` + +If an older unmarked `## Translation Quality Review` section already exists, replace it with the marked block. Do not append duplicates. Do not change the marker casing. Do not place generated workflow footers, integrity notes, or unrelated comments inside the managed block. + +Do not use any other marker names or casing. In particular, never use `TRANSLATION-REVIEW-START`, `TRANSLATION-REVIEW-END`, `TRANSLATION-QUALITY-REVIEW-START`, or uppercase marker variants. + +Use this format: + +```markdown + +## Translation Quality Review + +**Translation status:** Accepted +**Files reviewed:** 34 total, 34 accepted, 0 needs polish + +| Language | File | Final grade | Notes | +|---|---|---:|---| +| es | `translations/es/README.md` | A- | Preserves structure and reads naturally after polish. | + +All changed translated Markdown files must be graded **A- or higher** before this PR is marked `translation-polished`. + +``` + +Use `Translation status: Accepted` only when every changed translated Markdown file is graded A- or higher. Otherwise use `Translation status: Needs polish`, include counts for total files, accepted files, and files that need polish, and keep the `translation-polished` label off the PR. + +Include one row for every changed translated Markdown file in the target pull request. Keep notes concise and specific. For below-threshold files, the note must state the highest-impact issue to fix. + +## Pull request comments + +Do not add pull request comments. Use only the managed `## Translation Quality Review` pull request body section for grades, notes, and status. diff --git a/.gitignore b/.gitignore index d54b2511..9b6560e3 100644 --- a/.gitignore +++ b/.gitignore @@ -101,4 +101,5 @@ Desktop.ini .plans .plans.vhs-wrapper demo-previews -blog \ No newline at end of file +blog +migration.md \ No newline at end of file diff --git a/00-quick-start/README.md b/00-quick-start/README.md index a0e1da61..3891c9a9 100644 --- a/00-quick-start/README.md +++ b/00-quick-start/README.md @@ -1,4 +1,15 @@ -![Chapter 00: Quick Start](images/chapter-header.png) + + +![Chapter 00: Quick Start](assets/chapter-header.png) Welcome! In this chapter, you'll get GitHub Copilot CLI (Command Line Interface) installed, signed in with your GitHub account, and verified that everything works. This is a quick setup chapter. Once you're up and running, the real demos start in Chapter 01! @@ -87,6 +98,31 @@ Follow these steps if you'd like to run Copilot CLI on your local machine with t curl -fsSL https://gh.io/copilot-install | bash ``` +
+Optional: Enable shell tab completion + +Shell tab completion lets you press **Tab** to complete `copilot` subcommands, command options, and some option values. This is optional, but it can be handy once you're comfortable using the CLI. + +Copilot CLI currently supports completion scripts for Bash, Zsh, and Fish: + +```shell +# Bash, current session only +source <(copilot completion bash) + +# Bash, persistent on Linux +copilot completion bash | sudo tee /etc/bash_completion.d/copilot + +# Zsh +copilot completion zsh > "${fpath[1]}/_copilot" + +# Fish +copilot completion fish > ~/.config/fish/completions/copilot.fish +``` + +Restart your shell after adding persistent completion. PowerShell is supported for running Copilot CLI on Windows, but `copilot completion` currently supports only Bash, Zsh, and Fish. + +
+ --- ## Authentication @@ -99,7 +135,7 @@ copilot You'll be asked to trust the folder containing the repository (if you haven't already). You can trust it one time or across all future sessions. -Trusting files in a folder with the Copilot CLI +Trusting files in a folder with the Copilot CLI After trusting the folder, you can sign in with your GitHub account. @@ -115,7 +151,7 @@ After trusting the folder, you can sign in with your GitHub account. 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 +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.* @@ -144,7 +180,7 @@ After you receive a response, you can exit the CLI:
๐ŸŽฌ See it in action! -![Hello Demo](images/hello-demo.gif) +![Hello Demo](assets/hello-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* diff --git a/00-quick-start/images/auth-device-flow.png b/00-quick-start/assets/auth-device-flow.png similarity index 100% rename from 00-quick-start/images/auth-device-flow.png rename to 00-quick-start/assets/auth-device-flow.png diff --git a/00-quick-start/images/chapter-header.png b/00-quick-start/assets/chapter-header.png similarity index 100% rename from 00-quick-start/images/chapter-header.png rename to 00-quick-start/assets/chapter-header.png diff --git a/00-quick-start/images/copilot-trust.png b/00-quick-start/assets/copilot-trust.png similarity index 100% rename from 00-quick-start/images/copilot-trust.png rename to 00-quick-start/assets/copilot-trust.png diff --git a/00-quick-start/images/hello-demo.gif b/00-quick-start/assets/hello-demo.gif similarity index 100% rename from 00-quick-start/images/hello-demo.gif rename to 00-quick-start/assets/hello-demo.gif diff --git a/00-quick-start/images/hello-demo.tape b/00-quick-start/assets/hello-demo.tape similarity index 100% rename from 00-quick-start/images/hello-demo.tape rename to 00-quick-start/assets/hello-demo.tape diff --git a/01-setup-and-first-steps/README.md b/01-setup-and-first-steps/README.md index 1f54dc44..ba53741b 100644 --- a/01-setup-and-first-steps/README.md +++ b/01-setup-and-first-steps/README.md @@ -1,4 +1,15 @@ -![Chapter 01: First Steps](images/chapter-header.png) + + +![Chapter 01: First Steps](assets/chapter-header.png) > **Watch AI find bugs instantly, explain confusing code, and generate working scripts. Then learn three different ways to use GitHub Copilot CLI.** @@ -20,7 +31,7 @@ By the end of this chapter, you'll be able to: # Your First Copilot CLI Experience -Developer sitting at a desk with code on the monitor and glowing particles representing AI assistance +Developer sitting at a desk with code on the monitor and glowing particles representing AI assistance Jump right in and see what Copilot CLI can do. @@ -86,7 +97,7 @@ Once inside the interactive Copilot CLI session, run the following:
๐ŸŽฌ See it in action! -![Code Review Demo](images/code-review-demo.gif) +![Code Review Demo](assets/code-review-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -111,7 +122,7 @@ Ever stared at code wondering what it does? Try this in your Copilot CLI session
๐ŸŽฌ See it in action! -![Explain Code Demo](images/explain-code-demo.gif) +![Explain Code Demo](assets/explain-code-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -167,7 +178,7 @@ Need a function you'd otherwise spend 15 minutes googling? Still in your session
๐ŸŽฌ See it in action! -![Generate Code Demo](images/generate-code-demo.gif) +![Generate Code Demo](assets/generate-code-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -189,7 +200,7 @@ When you're done exploring, exit the session: # Modes and Commands -Futuristic control panel with glowing screens, dials, and equalizers representing Copilot CLI modes and commands +Futuristic control panel with glowing screens, dials, and equalizers representing Copilot CLI modes and commands You've just seen what Copilot CLI can do. Now let's understand *how* to use these capabilities effectively. The key is knowing which of the three interaction modes to use for different situations. @@ -209,7 +220,7 @@ Think of using GitHub Copilot CLI like going out to eat. From planning the trip Just like dining out, you'll naturally learn when each approach feels right. -Three Ways to Use GitHub Copilot CLI - Plan Mode (GPS route to restaurant), Interactive Mode (talking to waiter), Programmatic Mode (drive-through) +Three Ways to Use GitHub Copilot CLI - Plan Mode (GPS route to restaurant), Interactive Mode (talking to waiter), Programmatic Mode (drive-through) *Choose your mode based on the task: Plan for mapping it out first, Interactive for back-and-forth collaboration, Programmatic for quick one-shot results* @@ -230,7 +241,7 @@ Once you're comfortable, try: ### Mode 1: Interactive Mode (start here) -Interactive Mode - Like talking to a waiter who can answer questions and adjust the order +Interactive Mode - Like talking to a waiter who can answer questions and adjust the order **Best for**: Exploration, iteration, multi-turn conversations. Like talking to a waiter who can answer questions, take feedback, and adjust the order on the fly. @@ -268,7 +279,7 @@ Notice how each prompt builds on the previous answer. You're having a conversati ### Mode 2: Plan Mode -Plan Mode - Like planning a route before a trip using GPS +Plan Mode - Like planning a route before a trip using GPS **Best for**: Complex tasks where you want to review the approach before execution. Similar to planning a route before a trip using GPS. @@ -324,7 +335,7 @@ Proceed with implementation? [Y/n] ### Mode 3: Programmatic Mode -Programmatic Mode - Like using a drive-through for a quick order +Programmatic Mode - Like using a drive-through for a quick order **Best for**: Automation, scripts, CI/CD, single-shot commands. Like using a drive-through for a quick order without needing to talk to a waiter. @@ -363,17 +374,25 @@ copilot --allow-all -p "Review @myfile.py for issues" ## Essential Slash Commands -These commands work in interactive mode. **Start with just these six** - they cover 90% of daily use: +These commands are great to learn initially as you're getting started with Copilot CLI: | Command | What It Does | When to Use | |---------|--------------|-------------| +| `/ask` | Ask a quick question without it affecting your conversation history | When you want a quick answer without derailing your current task | | `/clear` | Clear conversation and start fresh | When switching topics | | `/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. > ๐Ÿ“š **Official Documentation**: [CLI command reference](https://docs.github.com/copilot/reference/cli-command-reference) for the complete list of commands and flags. @@ -388,8 +407,10 @@ That's it for getting started! As you become comfortable, you can explore additi | Command | What It Does | |---------|--------------| | `/agent` | Browse and select from available agents | +| `/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). @@ -418,7 +439,7 @@ That's it for getting started! As you become comfortable, you can explore additi | Command | What It Does | |---------|--------------| | `/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 | +| `/allow-all [on\|off\|show]` | Auto-approve all permission prompts; use `on` to enable, `off` to disable, `show` to check current status | | `/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 | @@ -430,24 +451,36 @@ That's it for getting started! As you become comfortable, you can explore additi | Command | What It Does | |---------|--------------| | `/clear` | Abandons the current session (no history saved) and starts a fresh conversation | -| `/compact` | Summarize conversation to reduce context usage | +| `/compact` | Summarize conversation to reduce context usage (optionally add focus instructions, e.g. `/compact focus on the bug list`) | | `/context` | Show context window token usage and visualization | +| `/keep-alive` | Prevent your system from sleeping while Copilot CLI is active โ€” handy for long-running tasks on a laptop | +| `/memory [on\|off\|show]` | Enable, disable, or view persistent memory โ€” facts and preferences remembered across all sessions | | `/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) | +| `/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 | -| `/usage` | Display session usage metrics and statistics | -| `/session` | Show session info and workspace summary | +| `/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
--- # Practice -Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice +Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice Time to put what you've learned into action. @@ -584,7 +621,7 @@ Additional details about remote sessions can be found in the [Copilot CLI docs]( The hands-on examples focused on reviewing and refactoring `book_app.py`. Now practice the same skills on a different file, `utils.py`: 1. Start an interactive session: `copilot` -2. Ask Copilot CLI to summarize the file: `@samples/book-app-project/utils.py What does each function in this file do?` +2. Ask Copilot CLI to summarize the file: "Summarize @samples/book-app-project/utils.py and explain what each function in this file does" 3. Ask it to add input validation: "Add validation to `get_user_choice()` so it handles empty input and non-numeric entries" 4. Ask it to improve error handling: "What happens if `get_book_details()` receives an empty string for the title? Add guards for that." 5. Ask for a docstring: "Add a comprehensive docstring to `get_book_details()` with parameter descriptions and return values" @@ -632,7 +669,8 @@ The examples used `/plan` for a search feature and `-p` for batch reviews. Now t |---------|--------------|-----| | Typing `exit` instead of `/exit` | Copilot CLI treats "exit" as a prompt, not a command | Slash commands always start with `/` | | Using `-p` for multi-turn conversations | Each `-p` call is isolated with no memory of previous calls | Use interactive mode (`copilot`) for conversations that build on context | -| Forgetting quotes around prompts with `$` or `!` | Shell interprets special characters before Copilot CLI sees them | Wrap prompts in quotes: `copilot -p "What does $HOME mean?"` | +| Forgetting quotes around prompts with `$` or `!` | Shell interprets special characters before Copilot CLI sees them | Wrap prompts in single quotes: `copilot -p 'What does $HOME mean?'` | +| Pressing Esc once to cancel a running task | A single Esc no longer cancels in-flight work (to prevent accidents) | Press **Esc twice** to cancel while Copilot CLI is processing | ### Troubleshooting @@ -653,7 +691,7 @@ The examples used `/plan` for a search feature and `-p` for batch reviews. Now t 1. **Interactive mode** is for exploration and iteration - context carries forward. It's like having a conversation with someone who remembers what you've said up to that point. 2. **Plan mode** is normally for more involved tasks. Review before implementation. 3. **Programmatic mode** is for automation. No interaction needed. -4. **Essential commands** (`/help`, `/clear`, `/plan`, `/research`, `/model`, `/exit`) cover most daily use. +4. **Essential commands** (`/ask`, `/help`, `/clear`, `/plan`, `/research`, `/model`, `/exit`) cover most daily use. > ๐Ÿ“‹ **Quick Reference**: See the [GitHub Copilot CLI command reference](https://docs.github.com/en/copilot/reference/cli-command-reference) for a complete list of commands and shortcuts. diff --git a/01-setup-and-first-steps/images/chapter-header.png b/01-setup-and-first-steps/assets/chapter-header.png similarity index 100% rename from 01-setup-and-first-steps/images/chapter-header.png rename to 01-setup-and-first-steps/assets/chapter-header.png diff --git a/01-setup-and-first-steps/images/code-review-demo.gif b/01-setup-and-first-steps/assets/code-review-demo.gif similarity index 100% rename from 01-setup-and-first-steps/images/code-review-demo.gif rename to 01-setup-and-first-steps/assets/code-review-demo.gif diff --git a/01-setup-and-first-steps/images/code-review-demo.tape b/01-setup-and-first-steps/assets/code-review-demo.tape similarity index 100% rename from 01-setup-and-first-steps/images/code-review-demo.tape rename to 01-setup-and-first-steps/assets/code-review-demo.tape diff --git a/01-setup-and-first-steps/images/explain-code-demo.gif b/01-setup-and-first-steps/assets/explain-code-demo.gif similarity index 100% rename from 01-setup-and-first-steps/images/explain-code-demo.gif rename to 01-setup-and-first-steps/assets/explain-code-demo.gif diff --git a/01-setup-and-first-steps/images/explain-code-demo.tape b/01-setup-and-first-steps/assets/explain-code-demo.tape similarity index 100% rename from 01-setup-and-first-steps/images/explain-code-demo.tape rename to 01-setup-and-first-steps/assets/explain-code-demo.tape diff --git a/01-setup-and-first-steps/images/first-copilot-experience.png b/01-setup-and-first-steps/assets/first-copilot-experience.png similarity index 100% rename from 01-setup-and-first-steps/images/first-copilot-experience.png rename to 01-setup-and-first-steps/assets/first-copilot-experience.png diff --git a/01-setup-and-first-steps/images/generate-code-demo.gif b/01-setup-and-first-steps/assets/generate-code-demo.gif similarity index 100% rename from 01-setup-and-first-steps/images/generate-code-demo.gif rename to 01-setup-and-first-steps/assets/generate-code-demo.gif diff --git a/01-setup-and-first-steps/images/generate-code-demo.tape b/01-setup-and-first-steps/assets/generate-code-demo.tape similarity index 100% rename from 01-setup-and-first-steps/images/generate-code-demo.tape rename to 01-setup-and-first-steps/assets/generate-code-demo.tape diff --git a/01-setup-and-first-steps/images/interactive-mode.png b/01-setup-and-first-steps/assets/interactive-mode.png similarity index 100% rename from 01-setup-and-first-steps/images/interactive-mode.png rename to 01-setup-and-first-steps/assets/interactive-mode.png diff --git a/01-setup-and-first-steps/images/modes-and-commands.png b/01-setup-and-first-steps/assets/modes-and-commands.png similarity index 100% rename from 01-setup-and-first-steps/images/modes-and-commands.png rename to 01-setup-and-first-steps/assets/modes-and-commands.png diff --git a/01-setup-and-first-steps/images/ordering-food-analogy.png b/01-setup-and-first-steps/assets/ordering-food-analogy.png similarity index 100% rename from 01-setup-and-first-steps/images/ordering-food-analogy.png rename to 01-setup-and-first-steps/assets/ordering-food-analogy.png diff --git a/01-setup-and-first-steps/images/plan-mode.png b/01-setup-and-first-steps/assets/plan-mode.png similarity index 100% rename from 01-setup-and-first-steps/images/plan-mode.png rename to 01-setup-and-first-steps/assets/plan-mode.png diff --git a/01-setup-and-first-steps/images/programmatic-mode.png b/01-setup-and-first-steps/assets/programmatic-mode.png similarity index 100% rename from 01-setup-and-first-steps/images/programmatic-mode.png rename to 01-setup-and-first-steps/assets/programmatic-mode.png diff --git a/02-context-conversations/README.md b/02-context-conversations/README.md index 81bfe7a9..60397cbb 100644 --- a/02-context-conversations/README.md +++ b/02-context-conversations/README.md @@ -1,4 +1,15 @@ -![Chapter 02: Context and Conversations](images/chapter-header.png) + + +![Chapter 02: Context and Conversations](assets/chapter-header.png) > **What if AI could see your entire codebase, not just one file at a time?** @@ -20,7 +31,7 @@ By the end of this chapter, you'll be able to: ## ๐Ÿงฉ Real-World Analogy: Working with a Colleague -Context Makes the Difference - Without vs With Context +Context Makes the Difference - Without vs With Context *Just like your colleagues, Copilot CLI isn't a mind reader. Providing more information helps humans and Copilot alike provide targeted support!* @@ -36,7 +47,7 @@ To provide context to Copilot CLI use *the `@` syntax* to point Copilot CLI at s # Essential: Basic Context -Glowing code blocks connected by light trails representing how context flows through Copilot CLI conversations +Glowing code blocks connected by light trails representing how context flows through Copilot CLI conversations This section covers everything you need to work effectively with context. Master these basics first. @@ -89,7 +100,7 @@ copilot
๐ŸŽฌ See it in action! -![File Context Demo](images/file-context-demo.gif) +![File Context Demo](assets/file-context-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -119,7 +130,7 @@ copilot This is where context becomes a superpower. Single-file analysis is useful. Cross-file analysis is transformative. -Cross-File Intelligence - comparing single-file vs cross-file analysis showing how analyzing files together reveals bugs, data flow, and patterns invisible in isolation +Cross-File Intelligence - comparing single-file vs cross-file analysis showing how analyzing files together reveals bugs, data flow, and patterns invisible in isolation ### Demo: Find Bugs That Span Multiple Files @@ -142,7 +153,7 @@ copilot
๐ŸŽฌ See it in action! -![Multi-File Demo](images/multi-file-demo.gif) +![Multi-File Demo](assets/multi-file-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -185,7 +196,7 @@ Cross-Module Analysis ### Demo: Understand a Codebase in 60 Seconds -Split-screen comparison showing manual code review taking 1 hour versus AI-assisted analysis taking 10 seconds +Split-screen comparison showing manual code review taking 1 hour versus AI-assisted analysis taking 10 seconds New to a project? Learn about it quickly using Copilot CLI. @@ -253,7 +264,7 @@ copilot
๐ŸŽฌ See a multi-turn conversation in action! -![Multi-Turn Demo](images/multi-turn-demo.gif) +![Multi-Turn Demo](assets/multi-turn-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -303,13 +314,19 @@ copilot --continue # Pick from a list of sessions interactively copilot --resume +# -r is a shorthand for --resume (saves some typing!) +copilot -r + # Or resume a specific session by ID -copilot --resume abc123 +copilot --resume=abc123 + +# Or resume by the name you gave the session +copilot --resume="my book app review" ``` > ๐Ÿ’ก **How do I find a session ID?** You don't need to memorize them. Running `copilot --resume` without an ID shows an interactive list of your previous sessions, their names, IDs, and when they were last active. Just pick the one you want. > -> **What about multiple terminals?** Each terminal window is its own session with its own context. If you have Copilot CLI open in three terminals, that's three separate sessions. Running `--resume` from any terminal lets you browse all of them. The `--continue` flag grabs whichever session was closed most recently, no matter which terminal it was in. +> **What about multiple terminals?** Each terminal window is its own session with its own context. If you have Copilot CLI open in three terminals, that's three separate sessions. Running `--resume` from any terminal lets you browse all of them. The `--continue` flag grabs the session from the current working directory first; if none exists there, it picks the most recently active session. > > **Can I switch sessions without restarting?** Yes. Use the `/resume` slash command from inside an active session: > ``` @@ -319,15 +336,56 @@ copilot --resume abc123 ### Organize Your Sessions -Give sessions meaningful names so you can find them later: +Give sessions meaningful names so you can find them later. You can name a session when you start it, or rename it at any time while inside the session: ```bash +# Name a session right when you start it +copilot --name book-app-review + +# Or rename the current session from inside copilot > /rename book-app-review # Session renamed for easier identification ``` +Once a session is named, you can resume it directly by name without browsing through a list: + +```bash +copilot --resume=book-app-review +``` + +To clean up sessions you no longer need, use `/session delete` from inside a session: + +```bash +copilot + +> /session delete # Deletes the current session +> /session delete abc123 # Deletes a specific session by ID +> /session delete-all # Deletes all sessions (use with care!) +``` + +### Persistent Memory Across Sessions + +Sessions save your conversation history, but **memory** goes one step further and lets Copilot CLI remember preferences and facts *across all sessions*, not just within a single one. + +```bash +copilot + +> /memory show +# Shows what Copilot CLI currently remembers about you and your project + +> /memory on +# Enables memory (on by default if your account supports it) + +> /memory off +# Disables memory (useful if you prefer a fresh slate each time) +``` + +For example, if you tell Copilot CLI "I always prefer pytest for Python testing", it can remember that preference and apply it automatically in future sessions. All without you having to repeat it. + +> ๐Ÿ’ก **Memory vs. Sessions**: Sessions save conversation history so you can resume a specific task. Memory saves reusable repository facts and user preferences that Copilot can apply in future work. Think of sessions as task notebooks, and memory as reusable context Copilot can carry forward. + ### Check and Manage Context As you add files and conversation, Copilot CLI's [context window](../GLOSSARY.md#context-window) fills up. Several commands are available to help you stay in control: @@ -356,17 +414,16 @@ Context usage: 62k/200k tokens (31%) ### Pick Up Where You Left Off -Timeline showing how GitHub Copilot CLI sessions persist across days - start on Monday, resume on Wednesday with full context restored +Timeline showing how GitHub Copilot CLI sessions persist across days - start on Monday, resume on Wednesday with full context restored *Sessions auto-save when you exit. Resume days later with full context: files, issues, and progress all remembered.* Imagine this workflow across multiple days: ```bash -# Monday: Start book app review -copilot +# Monday: Start book app review with a name right from the beginning +copilot --name book-app-review -> /rename book-app-review > @samples/book-app-project/books.py > Review and number all code quality issues @@ -384,8 +441,8 @@ Quality Issues Found: ``` ```bash -# Wednesday: Resume exactly where you left off -copilot --continue +# Wednesday: Resume exactly where you left off, by name +copilot --resume=book-app-review > What issues remain unfixed from our book app review? @@ -410,13 +467,13 @@ No re-explaining. No re-reading files. Just continue working. --- -**๐ŸŽ‰ You now know the essentials!** The `@` syntax, session management (`--continue`/`--resume`/`/rename`), and context commands (`/context`/`/clear`) are enough to be highly productive. Everything below is optional. Return to it when you're ready. +**๐ŸŽ‰ You now know the essentials!** The `@` syntax, session management (`--name`/`--continue`/`--resume`/`/rename`), and context commands (`/context`/`/clear`) are enough to be highly productive. Everything below is optional. Return to it when you're ready. --- # Optional: Going Deeper -Abstract crystal cave in blue and purple tones representing deeper exploration of context concepts +Abstract crystal cave in blue and purple tones representing deeper exploration of context concepts These topics build on the essentials above. **Pick what interests you, or skip ahead to [Practice](#practice).** @@ -528,7 +585,7 @@ You already know `/context` and `/clear` from the essentials. Here's the deeper Every AI has a "context window," which is the amount of text it can consider at once. -Context Window Visualization +Context Window Visualization *The context window is like a desk: it can only hold so much at once. Files, conversation history, and system prompts all take space.* @@ -566,6 +623,17 @@ copilot # Your key findings and decisions are preserved ``` +You can also give `/compact` optional focus instructions to shape what gets prioritized in the summary: + +```bash +copilot + +> /compact focus on the list of bugs we found and decisions made +# Summarizes history, keeping bug list and decisions prominent +``` + +> ๐Ÿ’ก **When to use focus instructions**: If your conversation covered many topics, focus instructions help `/compact` retain the parts most relevant to your next steps so you don't lose the thread. + #### Context Efficiency Tips | Situation | Action | Why | @@ -680,9 +748,9 @@ You can include images in your conversations using the `@` syntax, or simply **p ```bash copilot -> @images/screenshot.png What is happening in this image? +> @assets/screenshot.png What is happening in this image? -> @images/mockup.png Write the HTML and CSS to match this design. Place it in a new file called index.html and put the CSS in styles.css. +> @assets/mockup.png Write the HTML and CSS to match this design. Place it in a new file called index.html and put the CSS in styles.css. ``` > ๐Ÿ“– **Learn more**: See [Additional Context Features](../appendices/additional-context.md#working-with-images) for supported formats, practical use cases, and tips for combining images with code. @@ -693,7 +761,7 @@ copilot # Practice -Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice +Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice Time to apply your context and session management skills. @@ -858,10 +926,11 @@ copilot --add-dir /path/to/directory 1. **`@` syntax** gives Copilot CLI context about files, directories, and images 2. **Multi-turn conversations** build on each other as context accumulates -3. **Sessions auto-save**: use `--continue` or `--resume` to pick up where you left off -4. **Context windows** have limits: manage them with `/clear`, `/compact`, `/context`, `/new`, and `/rewind` -5. **Permission flags** (`--add-dir`, `--allow-all`) control multi-directory access. Use them wisely! -6. **Image references** (`@screenshot.png`) help debug UI issues visually +3. **Sessions auto-save**: name them at startup with `--name`, resume by name with `--resume=`, or use `--continue` to pick up the most recent session +4. **Context windows** have limits: manage them with `/clear`, `/compact`, `/context`, `/new`, and `/rewind`. Use `/compact focus on ` to shape what gets kept in the summary +5. **Persistent memory** (`/memory`) lets Copilot CLI remember preferences and facts across *all* sessions โ€” not just the current one +6. **Permission flags** (`--add-dir`, `--allow-all`) control multi-directory access. Use them wisely! +7. **Image references** (`@screenshot.png`) help debug UI issues visually > ๐Ÿ“š **Official Documentation**: [Use Copilot CLI](https://docs.github.com/copilot/how-tos/copilot-cli/use-copilot-cli) for the complete reference on context, sessions, and working with files. diff --git a/02-context-conversations/images/chapter-header.png b/02-context-conversations/assets/chapter-header.png similarity index 100% rename from 02-context-conversations/images/chapter-header.png rename to 02-context-conversations/assets/chapter-header.png diff --git a/02-context-conversations/images/codebase-understanding.png b/02-context-conversations/assets/codebase-understanding.png similarity index 100% rename from 02-context-conversations/images/codebase-understanding.png rename to 02-context-conversations/assets/codebase-understanding.png diff --git a/02-context-conversations/images/colleague-context-analogy.png b/02-context-conversations/assets/colleague-context-analogy.png similarity index 100% rename from 02-context-conversations/images/colleague-context-analogy.png rename to 02-context-conversations/assets/colleague-context-analogy.png diff --git a/02-context-conversations/images/context-window-visualization.png b/02-context-conversations/assets/context-window-visualization.png similarity index 100% rename from 02-context-conversations/images/context-window-visualization.png rename to 02-context-conversations/assets/context-window-visualization.png diff --git a/02-context-conversations/images/cross-file-intelligence.png b/02-context-conversations/assets/cross-file-intelligence.png similarity index 100% rename from 02-context-conversations/images/cross-file-intelligence.png rename to 02-context-conversations/assets/cross-file-intelligence.png diff --git a/02-context-conversations/images/essential-basic-context.png b/02-context-conversations/assets/essential-basic-context.png similarity index 100% rename from 02-context-conversations/images/essential-basic-context.png rename to 02-context-conversations/assets/essential-basic-context.png diff --git a/02-context-conversations/images/file-context-demo.gif b/02-context-conversations/assets/file-context-demo.gif similarity index 100% rename from 02-context-conversations/images/file-context-demo.gif rename to 02-context-conversations/assets/file-context-demo.gif diff --git a/02-context-conversations/images/file-context-demo.tape b/02-context-conversations/assets/file-context-demo.tape similarity index 100% rename from 02-context-conversations/images/file-context-demo.tape rename to 02-context-conversations/assets/file-context-demo.tape diff --git a/02-context-conversations/images/multi-file-demo.gif b/02-context-conversations/assets/multi-file-demo.gif similarity index 100% rename from 02-context-conversations/images/multi-file-demo.gif rename to 02-context-conversations/assets/multi-file-demo.gif diff --git a/02-context-conversations/images/multi-file-demo.tape b/02-context-conversations/assets/multi-file-demo.tape similarity index 100% rename from 02-context-conversations/images/multi-file-demo.tape rename to 02-context-conversations/assets/multi-file-demo.tape diff --git a/02-context-conversations/images/multi-turn-demo.gif b/02-context-conversations/assets/multi-turn-demo.gif similarity index 100% rename from 02-context-conversations/images/multi-turn-demo.gif rename to 02-context-conversations/assets/multi-turn-demo.gif diff --git a/02-context-conversations/images/multi-turn-demo.tape b/02-context-conversations/assets/multi-turn-demo.tape similarity index 100% rename from 02-context-conversations/images/multi-turn-demo.tape rename to 02-context-conversations/assets/multi-turn-demo.tape diff --git a/02-context-conversations/images/optional-going-deeper.png b/02-context-conversations/assets/optional-going-deeper.png similarity index 100% rename from 02-context-conversations/images/optional-going-deeper.png rename to 02-context-conversations/assets/optional-going-deeper.png diff --git a/02-context-conversations/images/session-persistence-timeline.png b/02-context-conversations/assets/session-persistence-timeline.png similarity index 100% rename from 02-context-conversations/images/session-persistence-timeline.png rename to 02-context-conversations/assets/session-persistence-timeline.png diff --git a/03-development-workflows/README.md b/03-development-workflows/README.md index befa2fc0..0116c50f 100644 --- a/03-development-workflows/README.md +++ b/03-development-workflows/README.md @@ -1,4 +1,15 @@ -![Chapter 03: Development Workflows](images/chapter-header.png) + + +![Chapter 03: Development Workflows](assets/chapter-header.png) > **What if the AI could find bugs you didn't even know to ask about?** @@ -22,7 +33,7 @@ By the end of this chapter, you'll be able to: A carpenter doesn't just know how to use tools, they have *workflows* for different jobs: -Craftsman workshop showing three workflow lanes: Building Furniture (Measure, Cut, Assemble, Finish), Fixing Damage (Assess, Remove, Repair, Match), and Quality Check (Inspect, Test Joints, Check Alignment) +Craftsman workshop showing three workflow lanes: Building Furniture (Measure, Cut, Assemble, Finish), Fixing Damage (Assess, Remove, Repair, Match), and Quality Check (Inspect, Test Joints, Check Alignment) Similarly, developers have workflows for different tasks. GitHub Copilot CLI enhances each of these workflows, making you more efficient and effective in your daily coding tasks. @@ -30,7 +41,7 @@ Similarly, developers have workflows for different tasks. GitHub Copilot CLI enh # The Five Workflows -Five glowing neon icons representing code review, testing, debugging, refactoring, and git integration workflows +Five glowing neon icons representing code review, testing, debugging, refactoring, and git integration workflows Each workflow below is self-contained. Pick the ones that match your current needs, or work through them all. @@ -40,7 +51,7 @@ Each workflow below is self-contained. Pick the ones that match your current nee This chapter covers five workflows that developers typically use. **However, you don't need to read them all at once!** Each workflow is self-contained in a collapsible section below. Pick the ones that match what you need and that fits best with your current project. You can always come back and explore the others later. -Five Development Workflows: Code Review, Refactoring, Debugging, Test Generation, and Git Integration shown as horizontal swimlanes +Five Development Workflows: Code Review, Refactoring, Debugging, Test Generation, and Git Integration shown as horizontal swimlanes | I want to... | Jump to | |---|---| @@ -60,7 +71,7 @@ This chapter covers five workflows that developers typically use. **However, you
Workflow 1: Code Review - Review files, use the /review agent, create severity checklists -Code review workflow: review, identify issues, prioritize, generate checklist. +Code review workflow: review, identify issues, prioritize, generate checklist. ### Basic Review @@ -77,7 +88,7 @@ copilot
๐ŸŽฌ See it in action! -![Code Review Demo](images/code-review-demo.gif) +![Code Review Demo](assets/code-review-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -185,7 +196,7 @@ copilot
Workflow 2: Refactoring - Restructure code, separate concerns, improve error handling -Refactoring workflow: assess code, plan changes, implement, verify behavior. +Refactoring workflow: assess code, plan changes, implement, verify behavior. ### Simple Refactoring @@ -210,7 +221,7 @@ copilot
๐ŸŽฌ See it in action! -![Refactor Demo](images/refactor-demo.gif) +![Refactor Demo](assets/refactor-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -278,7 +289,7 @@ copilot
Workflow 3: Debugging - Track down bugs, security audits, trace issues across files -Debugging workflow: understand error, locate root cause, fix, test. +Debugging workflow: understand error, locate root cause, fix, test. ### Simple Debugging @@ -306,7 +317,7 @@ copilot
๐ŸŽฌ See it in action! -![Fix Bug Demo](images/fix-bug-demo.gif) +![Fix Bug Demo](assets/fix-bug-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -420,7 +431,7 @@ copilot
Workflow 4: Test Generation - Generate comprehensive tests and edge cases automatically -Test Generation workflow: analyze function, generate tests, include edge cases, run. +Test Generation workflow: analyze function, generate tests, include edge cases, run. > **Try this first:** `@samples/book-app-project/books.py Generate pytest tests for all functions including edge cases` @@ -450,7 +461,7 @@ copilot
๐ŸŽฌ See it in action! -![Test Generation Demo](images/test-gen-demo.gif) +![Test Generation Demo](assets/test-gen-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -571,9 +582,9 @@ copilot
-Workflow 5: Git Integration - Commit messages, PR descriptions, /pr, /delegate, and /diff +Workflow 5: Git Integration - Commit messages, PR descriptions, /pr, /delegate, /diff, and /branch -Git Integration workflow: stage changes, generate message, commit, create PR. +Git Integration workflow: stage changes, generate message, commit, create PR. > ๐Ÿ’ก **This workflow assumes basic git familiarity** (staging, committing, branches). If git is new to you, try the other four workflows first. @@ -604,7 +615,7 @@ copilot -p "Generate a conventional commit message for: $(git diff --staged)"
๐ŸŽฌ See it in action! -![Git Integration Demo](images/git-integration-demo.gif) +![Git Integration Demo](assets/git-integration-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -680,7 +691,7 @@ This is great for well-defined tasks you want completed while you focus on other ### Using /diff to Review Session Changes -The `/diff` command shows all changes made during your current session. Use this slash command to see a visual diff of everything Copilot CLI has modified before you commit. +The `/diff` command shows all changes made during your current session. Use this slash command to see a visual diff of everything Copilot CLI has modified before you commit. It also works in folders that aren't git repositories. ```bash copilot @@ -692,6 +703,28 @@ copilot # Great for reviewing before committing ``` +### Branching Your Session with /branch or /fork + +Sometimes you want to explore two different approaches to a problem without losing your original conversation. The `/branch` command (also available as `/fork`) creates a copy of your current session so you can try a different direction and then compare results. + +```bash +copilot + +> Fix the find_by_author function to support partial matches + +# You want to try a different approach โ€” branch first! +> /branch + +# Now you're in a new session copy. Try your alternative approach: +> Fix find_by_author using a different regex-based strategy + +# If you don't like the result, switch back to your original session using /session +``` + +> ๐Ÿ’ก **`/branch` and `/fork` are the same**: Both commands do identical things. `/branch` was added as a more intuitive name. Use whichever makes more sense to you. + +> ๐Ÿ’ก **When to branch**: Branching is great when you're unsure which approach is better and want to keep both options open. +
--- @@ -724,7 +757,7 @@ copilot > Users report: 'Finding books by author name doesn't work for partial names' > @samples/book-app-project/books.py Analyze and identify the likely cause -# 2. Debug the issue (continuing in same session) +# 2. Debug the issue and fix (continuing in same session) > Based on the analysis, show me the find_by_author function and explain the issue > Fix the find_by_author function to handle partial name matches @@ -736,10 +769,23 @@ copilot > - Case-insensitive matching > - Author name not found -# 4. Generate commit message +# Exit the interactive session + +> /exit + +# 4. Run git add + +# Stage the changes so git diff --staged has something to work with +git add . + +# 5. Generate commit message copilot -p "Generate commit message for: $(git diff --staged)" -# Output: "fix(books): support partial author name search" +# Example Output: "fix(books): support partial author name search" + +# 6. Commit changes (optional) + +git commit -m "" ``` ### Bug Fix Workflow Summary @@ -747,16 +793,17 @@ copilot -p "Generate commit message for: $(git diff --staged)" | Step | Action | Copilot Command | |------|--------|-----------------| | 1 | Understand the bug | `> [describe bug] @relevant-file.py Analyze the likely cause` | -| 2 | Get detailed analysis | `> Show me the function and explain the issue` | -| 3 | Implement the fix | `> Fix the [specific issue]` | -| 4 | Generate tests | `> Generate tests for [specific scenarios]` | -| 5 | Commit | `copilot -p "Generate commit message for: $(git diff --staged)"` | +| 2 | Analysis and fix | `> Show me the function and fix the issue` | +| 3 | Generate tests | `> Generate tests for [specific scenarios]` | +| 4 | Stage changes | `git add .` | +| 5 | Generate commit message | `copilot -p "Generate commit message for: $(git diff --staged)"` | +| 6 | Commit changes| `git commit -m ""` | --- # Practice -Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice +Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice Now it's your turn to apply these workflows. @@ -836,7 +883,7 @@ copilot The exercise shows developers how to use GitHub Copilot CLI to create issues, generate code, and collaborate from the terminal while building a Node.js calculator app. You'll install the CLI, use templates and agents, and practice iterative, command-line driven development. -##### [Start the "Create applications with the Copilot CLI" Skills Exercise](https://github.com/skills/create-applications-with-the-copilot-cli) +##### [Start the "Create applications with the Copilot CLI" Skills Exercise](https://github.com/skills/create-applications-with-the-copilot-cli) --- @@ -890,7 +937,7 @@ copilot ## ๐Ÿ”‘ Key Takeaways -Specialized Workflows for Every Task: Code Review, Refactoring, Debugging, Testing, and Git Integration +Specialized Workflows for Every Task: Code Review, Refactoring, Debugging, Testing, and Git Integration 1. **Code review** becomes comprehensive with specific prompts 2. **Refactoring** is safer when you generate tests first diff --git a/03-development-workflows/images/carpenter-workflow-steps.png b/03-development-workflows/assets/carpenter-workflow-steps.png similarity index 100% rename from 03-development-workflows/images/carpenter-workflow-steps.png rename to 03-development-workflows/assets/carpenter-workflow-steps.png diff --git a/03-development-workflows/images/chapter-header.png b/03-development-workflows/assets/chapter-header.png similarity index 100% rename from 03-development-workflows/images/chapter-header.png rename to 03-development-workflows/assets/chapter-header.png diff --git a/03-development-workflows/images/code-review-demo.gif b/03-development-workflows/assets/code-review-demo.gif similarity index 100% rename from 03-development-workflows/images/code-review-demo.gif rename to 03-development-workflows/assets/code-review-demo.gif diff --git a/03-development-workflows/images/code-review-demo.tape b/03-development-workflows/assets/code-review-demo.tape similarity index 100% rename from 03-development-workflows/images/code-review-demo.tape rename to 03-development-workflows/assets/code-review-demo.tape diff --git a/03-development-workflows/images/code-review-swimlane-single.png b/03-development-workflows/assets/code-review-swimlane-single.png similarity index 100% rename from 03-development-workflows/images/code-review-swimlane-single.png rename to 03-development-workflows/assets/code-review-swimlane-single.png diff --git a/03-development-workflows/images/debugging-swimlane-single.png b/03-development-workflows/assets/debugging-swimlane-single.png similarity index 100% rename from 03-development-workflows/images/debugging-swimlane-single.png rename to 03-development-workflows/assets/debugging-swimlane-single.png diff --git a/03-development-workflows/images/five-workflows-swimlane.png b/03-development-workflows/assets/five-workflows-swimlane.png similarity index 100% rename from 03-development-workflows/images/five-workflows-swimlane.png rename to 03-development-workflows/assets/five-workflows-swimlane.png diff --git a/03-development-workflows/images/five-workflows.png b/03-development-workflows/assets/five-workflows.png similarity index 100% rename from 03-development-workflows/images/five-workflows.png rename to 03-development-workflows/assets/five-workflows.png diff --git a/03-development-workflows/images/fix-bug-demo.gif b/03-development-workflows/assets/fix-bug-demo.gif similarity index 100% rename from 03-development-workflows/images/fix-bug-demo.gif rename to 03-development-workflows/assets/fix-bug-demo.gif diff --git a/03-development-workflows/images/fix-bug-demo.tape b/03-development-workflows/assets/fix-bug-demo.tape similarity index 100% rename from 03-development-workflows/images/fix-bug-demo.tape rename to 03-development-workflows/assets/fix-bug-demo.tape diff --git a/03-development-workflows/images/git-integration-demo.gif b/03-development-workflows/assets/git-integration-demo.gif similarity index 100% rename from 03-development-workflows/images/git-integration-demo.gif rename to 03-development-workflows/assets/git-integration-demo.gif diff --git a/03-development-workflows/images/git-integration-demo.tape b/03-development-workflows/assets/git-integration-demo.tape similarity index 100% rename from 03-development-workflows/images/git-integration-demo.tape rename to 03-development-workflows/assets/git-integration-demo.tape diff --git a/03-development-workflows/images/git-integration-swimlane-single.png b/03-development-workflows/assets/git-integration-swimlane-single.png similarity index 100% rename from 03-development-workflows/images/git-integration-swimlane-single.png rename to 03-development-workflows/assets/git-integration-swimlane-single.png diff --git a/03-development-workflows/images/refactor-demo.gif b/03-development-workflows/assets/refactor-demo.gif similarity index 100% rename from 03-development-workflows/images/refactor-demo.gif rename to 03-development-workflows/assets/refactor-demo.gif diff --git a/03-development-workflows/images/refactor-demo.tape b/03-development-workflows/assets/refactor-demo.tape similarity index 100% rename from 03-development-workflows/images/refactor-demo.tape rename to 03-development-workflows/assets/refactor-demo.tape diff --git a/03-development-workflows/images/refactoring-swimlane-single.png b/03-development-workflows/assets/refactoring-swimlane-single.png similarity index 100% rename from 03-development-workflows/images/refactoring-swimlane-single.png rename to 03-development-workflows/assets/refactoring-swimlane-single.png diff --git a/03-development-workflows/images/request.json b/03-development-workflows/assets/request.json similarity index 100% rename from 03-development-workflows/images/request.json rename to 03-development-workflows/assets/request.json diff --git a/03-development-workflows/images/specialized-workflows.png b/03-development-workflows/assets/specialized-workflows.png similarity index 100% rename from 03-development-workflows/images/specialized-workflows.png rename to 03-development-workflows/assets/specialized-workflows.png diff --git a/03-development-workflows/images/test-gen-demo.gif b/03-development-workflows/assets/test-gen-demo.gif similarity index 100% rename from 03-development-workflows/images/test-gen-demo.gif rename to 03-development-workflows/assets/test-gen-demo.gif diff --git a/03-development-workflows/images/test-gen-demo.tape b/03-development-workflows/assets/test-gen-demo.tape similarity index 100% rename from 03-development-workflows/images/test-gen-demo.tape rename to 03-development-workflows/assets/test-gen-demo.tape diff --git a/03-development-workflows/images/test-gen-swimlane-single.png b/03-development-workflows/assets/test-gen-swimlane-single.png similarity index 100% rename from 03-development-workflows/images/test-gen-swimlane-single.png rename to 03-development-workflows/assets/test-gen-swimlane-single.png diff --git a/04-agents-custom-instructions/README.md b/04-agents-custom-instructions/README.md index a84d8894..44c93a93 100644 --- a/04-agents-custom-instructions/README.md +++ b/04-agents-custom-instructions/README.md @@ -1,4 +1,15 @@ -![Chapter 04: Agents and Custom Instructions](images/chapter-header.png) + + +![Chapter 04: Agents and Custom Instructions](assets/chapter-header.png) > **What if you could hire a Python code reviewer, testing expert, and security reviewer... all in one tool?** @@ -32,7 +43,7 @@ When you need help with your house, you don't call one "general helper." You cal Agents work the same way. Instead of a generic AI, use agents that focus on specific tasks and know the right process to follow. Set up the instructions once, then reuse them whenever you need that specialty: code review, testing, security, documentation. -Hiring Specialists Analogy - Just as you call specialized tradespeople for house repairs, AI agents are specialized for specific tasks like code review, testing, security, and documentation +Hiring Specialists Analogy - Just as you call specialized tradespeople for house repairs, AI agents are specialized for specific tasks like code review, testing, security, and documentation --- @@ -105,7 +116,7 @@ What about the Task Agent? It works behind the scenes to manage and track what i You can simply define your own agents to be part of your workflow! Define once, then direct! -Four colorful AI robots standing together, each with different tools representing specialized agent capabilities +Four colorful AI robots standing together, each with different tools representing specialized agent capabilities ## ๐Ÿ—‚๏ธ Add your agents @@ -185,11 +196,13 @@ copilot --agent python-reviewer > ๐Ÿ’ก **Switching agents**: You can switch to a different agent at any time by using `/agent` or `--agent` again. To return to the standard Copilot CLI experience, use `/agent` and select **no agent**. +> ๐Ÿ’ก **Agent mode is session-scoped**: The agent you select applies only to the current session. When you start a new session with `/new`, `/clear`, or by opening a fresh terminal, Copilot returns to its default mode โ€” your agent selection does not carry over automatically. This means each session starts with a clean slate, which is a good habit to keep your work focused. + --- # Going Deeper with Agents -Robot being assembled on a workbench surrounded by components and tools representing custom agent creation +Robot being assembled on a workbench surrounded by components and tools representing custom agent creation > ๐Ÿ’ก **This section is optional.** The built-in agents (`/plan`, `/review`) are powerful enough for most workflows. Create custom agents when you need specialized expertise that's consistently applied across your work. @@ -337,7 +350,7 @@ copilot
๐ŸŽฌ See it in action! -![Python Reviewer Demo](images/python-reviewer-demo.gif) +![Python Reviewer Demo](assets/python-reviewer-demo.gif) *Demo output varies - your model, tools, and responses will differ from what's shown here.* @@ -386,7 +399,7 @@ Think of it this way: agents are specialists you call on, and instruction files You already know the two main locations (see [Where to put agent files](#where-to-put-agent-files) above). Use this decision tree to choose: -Decision tree for where to put agent files: experimenting โ†’ current folder, team use โ†’ .github/agents/, everywhere โ†’ ~/.copilot/agents/ +Decision tree for where to put agent files: experimenting โ†’ current folder, team use โ†’ .github/agents/, everywhere โ†’ ~/.copilot/agents/ **Start simple:** Create a single `*.agent.md` file in your project folder. Move it to a permanent location once you're happy with it. @@ -422,6 +435,7 @@ Copilot will scan your project and create tailored instruction files. You can ed | `AGENTS.md` | Project root or nested | **Cross-platform standard** - works with Copilot and other AI assistants | | `.github/copilot-instructions.md` | Project | GitHub Copilot specific | | `.github/instructions/*.instructions.md` | Project | Granular, topic-specific instructions | +| `~/.copilot/instructions/**/*.instructions.md` | User (all projects) | Personal instructions that apply everywhere, across all your repos | | `CLAUDE.md`, `GEMINI.md` | Project root | Supported for compatibility | > ๐ŸŽฏ **Just getting started?** Use `AGENTS.md` for project instructions. You can explore the other formats later as needed. @@ -430,7 +444,7 @@ Copilot will scan your project and create tailored instruction files. You can ed `AGENTS.md` is the recommended format. It's an [open standard](https://agents.md/) that works across Copilot and other AI coding tools. Place it in your repository root and Copilot reads it automatically. This project's own [AGENTS.md](../AGENTS.md) is a working example. -A typical `AGENTS.md` describes your project context, code style, security requirements, and testing standards. Use `/init` to generate one, or write your own following the pattern in our example file. +A typical `AGENTS.md` describes your project context, code style, security requirements, and testing standards. Write your own following the pattern in our example file. ### Custom Instruction Files (.instructions.md) @@ -446,6 +460,48 @@ For teams that want more granular control, split instructions into topic-specifi > ๐Ÿ’ก **Note**: Instruction files work with any language. This example uses Python to match our course project, but you can create similar files for TypeScript, Go, Rust, or any technology your team uses. +#### Scoping Instructions with `applyTo` + +By default, an instruction file applies to every conversation. To limit it to specific file types, add an `applyTo` field in YAML frontmatter (the block between `---` markers at the very top of the file): + +```markdown +--- +applyTo: "**/*.py" +--- +# Python Standards +Always follow PEP 8 style conventions. +Use type hints in all function signatures. +``` + +With `applyTo: "**/*.py"`, Copilot only loads that instruction file when you are working with Python files. Instructions for Python style never clutter a conversation about, say, a Dockerfile or a SQL query. + +Here are some common patterns: + +| `applyTo` value | When it applies | +|---|---| +| `"**/*.py"` | Any Python file | +| `"**/*.{ts,tsx}"` | TypeScript and TSX files | +| `"tests/**"` | Any file inside a `tests/` folder | +| (no frontmatter) | Every conversation โ€” the default | + +> ๐Ÿ’ก **Tip**: Wrap the glob pattern in quotes (e.g., `"**/*.py"`) to ensure it is interpreted correctly across all operating systems and shells. + +#### Importing Other Files with `@` + +You can reference another file inside `AGENTS.md` or any instruction file using `@filepath` syntax. Copilot expands the reference and includes that file's content automatically, so you can keep your main file short while storing the details elsewhere: + +```markdown + +# Project Instructions + +@.github/instructions/python-standards.instructions.md +@.github/instructions/test-standards.instructions.md +``` + +This is handy when your instructions grow large. Split them into focused files and `@`-import them from a single `AGENTS.md`. The same syntax works inside `.github/copilot-instructions.md` and other instruction files too. + +> ๐Ÿ’ก **Tip**: Use `@`-imports to share a common base file across multiple instruction files. For example, you could have a `@.github/instructions/shared-rules.md` that every other instruction file pulls in. + **Finding community instruction files**: Browse [github/awesome-copilot](https://github.com/github/awesome-copilot) for pre-made instruction files covering .NET, Angular, Azure, Python, Docker, and many more technologies. ### Disabling Custom Instructions @@ -539,7 +595,7 @@ For community agents, see [github/awesome-copilot](https://github.com/github/awe # Practice -Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice +Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice Create your own agents and see them in action. diff --git a/04-agents-custom-instructions/images/agent-file-placement-decision-tree.png b/04-agents-custom-instructions/assets/agent-file-placement-decision-tree.png similarity index 100% rename from 04-agents-custom-instructions/images/agent-file-placement-decision-tree.png rename to 04-agents-custom-instructions/assets/agent-file-placement-decision-tree.png diff --git a/04-agents-custom-instructions/images/chapter-header.png b/04-agents-custom-instructions/assets/chapter-header.png similarity index 100% rename from 04-agents-custom-instructions/images/chapter-header.png rename to 04-agents-custom-instructions/assets/chapter-header.png diff --git a/04-agents-custom-instructions/images/creating-custom-agents.png b/04-agents-custom-instructions/assets/creating-custom-agents.png similarity index 100% rename from 04-agents-custom-instructions/images/creating-custom-agents.png rename to 04-agents-custom-instructions/assets/creating-custom-agents.png diff --git a/04-agents-custom-instructions/images/hiring-specialists-analogy.png b/04-agents-custom-instructions/assets/hiring-specialists-analogy.png similarity index 100% rename from 04-agents-custom-instructions/images/hiring-specialists-analogy.png rename to 04-agents-custom-instructions/assets/hiring-specialists-analogy.png diff --git a/04-agents-custom-instructions/images/python-reviewer-demo.gif b/04-agents-custom-instructions/assets/python-reviewer-demo.gif similarity index 100% rename from 04-agents-custom-instructions/images/python-reviewer-demo.gif rename to 04-agents-custom-instructions/assets/python-reviewer-demo.gif diff --git a/04-agents-custom-instructions/images/python-reviewer-demo.tape b/04-agents-custom-instructions/assets/python-reviewer-demo.tape similarity index 100% rename from 04-agents-custom-instructions/images/python-reviewer-demo.tape rename to 04-agents-custom-instructions/assets/python-reviewer-demo.tape diff --git a/04-agents-custom-instructions/images/using-agents.png b/04-agents-custom-instructions/assets/using-agents.png similarity index 100% rename from 04-agents-custom-instructions/images/using-agents.png rename to 04-agents-custom-instructions/assets/using-agents.png diff --git a/05-skills/README.md b/05-skills/README.md index 0cacd762..7036d2c7 100644 --- a/05-skills/README.md +++ b/05-skills/README.md @@ -1,4 +1,15 @@ -![Chapter 05: Skills System](images/chapter-header.png) + + +![Chapter 05: Skills System](assets/chapter-header.png) > **What if Copilot could automatically apply your team's best practices without you having to explain them every time?** @@ -21,7 +32,7 @@ By the end of this chapter, you'll be able to: ## ๐Ÿงฉ Real-World Analogy: Power Tools A general-purpose drill is useful, but specialized attachments make it powerful. -Power Tools - Skills Extend Copilot's Capabilities +Power Tools - Skills Extend Copilot's Capabilities Skills work the same way. Just like swapping drill bits for different jobs, you can add skills to Copilot for different tasks: @@ -41,7 +52,7 @@ Skills work the same way. Just like swapping drill bits for different jobs, you # How Skills Work -Glowing RPG-style skill icons connected by light trails on a starfield background representing Copilot skills +Glowing RPG-style skill icons connected by light trails on a starfield background representing Copilot skills Learn what skills are, why they matter, and how they differ from agents and MCP. @@ -101,6 +112,20 @@ While auto-triggering is the primary way skills work, you can also **invoke skil This gives you explicit control when you want to ensure a specific skill is used. +#### Combining Multiple Skills in One Message + +You can invoke **more than one skill in a single message**, and the skill slash command can appear anywhere in your prompt โ€” not just at the beginning. This is handy when you want two different checks done in one go: + +```bash +> Check @samples/book-app-project/book_app.py with /code-checklist and also run /generate-tests for it + +> Review the auth module /security-audit then /code-checklist the result +``` + +Copilot will apply each named skill in the same response, saving you from sending multiple separate messages. + +> ๐Ÿ’ก **Tip**: Put the skill slash commands wherever they feel most natural in your sentence. You can put them at the start, middle, or end of your message. + > ๐Ÿ“ **Skills vs Agents Invocation**: Don't confuse skill invocation with agent invocation: > - **Skills**: `/skill-name `, e.g., `/code-checklist Check this file` > - **Agents**: `/agent` (select from list) or `copilot --agent ` (command line) @@ -123,7 +148,7 @@ Skills are just one piece of GitHub Copilot's extensibility model. Here's how th > *Don't worry about MCP quite yet. We'll cover it in [Chapter 06](../06-mcp-servers/). It's included here so you can see how skills fit into the overall picture.* -Comparison diagram showing the differences between Agents, Skills, and MCP Servers and how they combine into your workflow +Comparison diagram showing the differences between Agents, Skills, and MCP Servers and how they combine into your workflow | Feature | What It Does | When to Use | |---------|--------------|-------------| @@ -178,7 +203,7 @@ copilot 3. Automatically loads your team's quality checklist 4. Applies all checks without you listing them -How Skills Auto-Trigger - 4-step flow showing how Copilot automatically matches your prompt to the right skill +How Skills Auto-Trigger - 4-step flow showing how Copilot automatically matches your prompt to the right skill *Just ask naturally. Copilot matches your prompt to the right skill and applies it automatically.* @@ -213,7 +238,7 @@ copilot
๐ŸŽฌ See it in action! -![Skill Trigger Demo](images/skill-trigger-demo.gif) +![Skill Trigger Demo](assets/skill-trigger-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -261,7 +286,7 @@ PR Review: feature/user-auth # Creating Custom Skills -Human and robotic hands building a wall of glowing LEGO-like blocks representing skill creation and management +Human and robotic hands building a wall of glowing LEGO-like blocks representing skill creation and management Build your own skills from SKILL.md files. @@ -344,6 +369,9 @@ Provide issues as a numbered list with severity: | `name` | **Yes** | Unique identifier (lowercase, hyphens for spaces) | | `description` | **Yes** | What the skill does and when Copilot should use it | | `license` | No | License that applies to this skill | +| `argument-hint` | No | Short hint shown to users describing what argument the skill expects (e.g., `"file path or code snippet"`) | + +> ๐Ÿ’ก **What is `argument-hint`?** When users invoke a skill directly (e.g., `/security-audit`), the `argument-hint` text appears as a placeholder showing what to type next โ€” like a mini help prompt. For example, setting `argument-hint: "file path to review"` tells the user to provide a file path after the skill name. > ๐Ÿ“– **Official docs**: [About Agent Skills](https://docs.github.com/copilot/concepts/agents/about-agent-skills) @@ -458,13 +486,33 @@ copilot --agent code-reviewer Discover installed skills, find community skills, and share your own. -Managing and Sharing Skills - showing the discover, use, create, and share cycle for CLI skills +Managing and Sharing Skills - showing the discover, use, create, and share cycle for CLI skills --- -## Managing Skills with the `/skills` Command +## Managing Skills with the `copilot skill` Command and `/skills` + +Copilot CLI gives you two ways to manage skills. You can do it directly from the terminal before starting Copilot or from inside a Copilot session. + +### Option 1: `copilot skill` (Terminal Command) + +The `copilot skill` subcommand lets you manage skills directly from your terminal, without opening an interactive Copilot session. This is handy for scripting, quick checks, or adding skills before you start working. + +```bash +# See all installed skills +copilot skill list -Use the `/skills` command to manage your installed skills: +# Add a skill from a local file, URL, or directory +copilot skill add .github/skills/my-skill/SKILL.md +copilot skill add https://example.com/skills/security-audit/SKILL.md + +# Remove a skill by name +copilot skill remove security-audit +``` + +### Option 2: `/skills` (Inside Copilot Session) + +Once you're in an interactive Copilot session, use `/skills` (or its shortcut `/skill`) to manage skills without leaving: | Command | What It Does | |---------|--------------| @@ -474,11 +522,23 @@ Use the `/skills` command to manage your installed skills: | `/skills remove ` | Disable or uninstall a skill | | `/skills reload` | Reload skills after editing SKILL.md files | +> ๐Ÿ’ก **`/skill` shortcut**: You can type `/skill` instead of `/skills` โ€” they're interchangeable. For example, `/skill list` works the same as `/skills list`. + > ๐Ÿ’ก **Remember**: You don't need to "activate" skills for each prompt. Once installed, skills are **automatically triggered** when your prompt matches their description. These commands are for managing which skills are available, not for using them. ### Example: View Your Skills ```bash +# From the terminal (no interactive session needed): +copilot skill list + +Available skills: +- security-audit: Security-focused code review checking OWASP Top 10 +- generate-tests: Generate comprehensive unit tests with edge cases +- code-checklist: Team code quality checklist +... + +# Or from inside a Copilot session: copilot > /skills list @@ -502,7 +562,7 @@ Description: Security-focused code review checking OWASP Top 10 vulnerabilities
See it in action! -![List Skills Demo](images/list-skills-demo.gif) +![List Skills Demo](assets/list-skills-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -546,7 +606,13 @@ copilot # Install a plugin from the marketplace ``` -Plugins can bundle multiple capabilities together - a single plugin might include related skills, agents, and MCP server configurations that work together. +To keep your local plugin catalog current, refresh it with: + +```bash +copilot plugin marketplace update +``` + +Plugins can bundle multiple capabilities together. A single plugin might include related skills, agents, and MCP server configurations that work together. ### Community Skill Repositories @@ -554,28 +620,28 @@ Pre-made skills are also available from community repositories: - **[Awesome Copilot](https://github.com/github/awesome-copilot)** - Official GitHub Copilot resources including skills documentation and examples -### Installing a Community Skill Manually +### Installing a Community Skill with GitHub CLI -If you find a skill in a GitHub repository, copy its folder into your skills directory: +The easiest way to install a skill from a GitHub repository is using the `gh skill install` command (requires [GitHub CLI v2.90.0+](https://github.blog/changelog/2026-04-16-manage-agent-skills-with-github-cli/)): ```bash -# Clone the awesome-copilot repository -git clone https://github.com/github/awesome-copilot.git /tmp/awesome-copilot +# Browse and interactively select a skill from awesome-copilot +gh skill install github/awesome-copilot -# Copy a specific skill to your project -cp -r /tmp/awesome-copilot/skills/code-checklist .github/skills/ +# Or install a specific skill directly +gh skill install github/awesome-copilot ai-ready -# Or for personal use across all projects -cp -r /tmp/awesome-copilot/skills/code-checklist ~/.copilot/skills/ +# Install for personal use across all projects (user scope) +gh skill install github/awesome-copilot ai-ready --scope user ``` -> โš ๏ธ **Review before installing**: Always read a skill's `SKILL.md` before copying it into your project. Skills control what Copilot does, and a malicious skill could instruct it to run harmful commands or modify code in unexpected ways. +> โš ๏ธ **Review before installing**: Always read a skill's `SKILL.md` before installing it. Skills control what Copilot does, and a malicious skill could instruct it to run harmful commands or modify code in unexpected ways. --- # Practice -Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice +Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice Apply what you've learned by building and testing your own skills. @@ -842,9 +908,10 @@ Run `/skills reload` after creating or editing skills to ensure changes are pick 1. **Skills are automatic**: Copilot loads them when your prompt matches the skill's description 2. **Direct invocation**: You can also invoke skills directly with `/skill-name` as a slash command -3. **SKILL.md format**: YAML frontmatter (name, description, optional license) plus markdown instructions +3. **SKILL.md format**: YAML frontmatter (name, description, optional license, argument-hint) plus markdown instructions 4. **Location matters**: `.github/skills/` for project/team sharing, `~/.copilot/skills/` for personal use 5. **Description is key**: Write descriptions that match how you naturally ask questions +6. **Two ways to manage skills**: Use `copilot skill` from the terminal or `/skills` (shortcut: `/skill`) inside a session > ๐Ÿ“‹ **Quick Reference**: See the [GitHub Copilot CLI command reference](https://docs.github.com/en/copilot/reference/cli-command-reference) for a complete list of commands and shortcuts. diff --git a/05-skills/images/chapter-header.png b/05-skills/assets/chapter-header.png similarity index 100% rename from 05-skills/images/chapter-header.png rename to 05-skills/assets/chapter-header.png diff --git a/05-skills/images/creating-managing-skills.png b/05-skills/assets/creating-managing-skills.png similarity index 100% rename from 05-skills/images/creating-managing-skills.png rename to 05-skills/assets/creating-managing-skills.png diff --git a/05-skills/images/how-skills-work.png b/05-skills/assets/how-skills-work.png similarity index 100% rename from 05-skills/images/how-skills-work.png rename to 05-skills/assets/how-skills-work.png diff --git a/05-skills/images/list-skills-demo.gif b/05-skills/assets/list-skills-demo.gif similarity index 100% rename from 05-skills/images/list-skills-demo.gif rename to 05-skills/assets/list-skills-demo.gif diff --git a/05-skills/images/list-skills-demo.tape b/05-skills/assets/list-skills-demo.tape similarity index 100% rename from 05-skills/images/list-skills-demo.tape rename to 05-skills/assets/list-skills-demo.tape diff --git a/05-skills/images/managing-sharing-skills.png b/05-skills/assets/managing-sharing-skills.png similarity index 100% rename from 05-skills/images/managing-sharing-skills.png rename to 05-skills/assets/managing-sharing-skills.png diff --git a/05-skills/images/power-tools-analogy.png b/05-skills/assets/power-tools-analogy.png similarity index 100% rename from 05-skills/images/power-tools-analogy.png rename to 05-skills/assets/power-tools-analogy.png diff --git a/05-skills/images/skill-auto-discovery-flow.png b/05-skills/assets/skill-auto-discovery-flow.png similarity index 100% rename from 05-skills/images/skill-auto-discovery-flow.png rename to 05-skills/assets/skill-auto-discovery-flow.png diff --git a/05-skills/images/skill-trigger-demo.gif b/05-skills/assets/skill-trigger-demo.gif similarity index 100% rename from 05-skills/images/skill-trigger-demo.gif rename to 05-skills/assets/skill-trigger-demo.gif diff --git a/05-skills/images/skill-trigger-demo.tape b/05-skills/assets/skill-trigger-demo.tape similarity index 100% rename from 05-skills/images/skill-trigger-demo.tape rename to 05-skills/assets/skill-trigger-demo.tape diff --git a/05-skills/images/skills-agents-mcp-comparison.png b/05-skills/assets/skills-agents-mcp-comparison.png similarity index 100% rename from 05-skills/images/skills-agents-mcp-comparison.png rename to 05-skills/assets/skills-agents-mcp-comparison.png diff --git a/06-mcp-servers/README.md b/06-mcp-servers/README.md index 391ed951..bfbc7f80 100644 --- a/06-mcp-servers/README.md +++ b/06-mcp-servers/README.md @@ -1,4 +1,15 @@ -![Chapter 06: MCP Servers](images/chapter-header.png) + + +![Chapter 06: MCP Servers](assets/chapter-header.png) > **What if Copilot could read your GitHub issues, check your database, and create PRs... all from the terminal?** @@ -24,7 +35,7 @@ By the end of this chapter, you'll be able to: ## ๐Ÿงฉ Real-World Analogy: Browser Extensions -MCP Servers are like Browser Extensions +MCP Servers are like Browser Extensions Think of MCP servers like browser extensions. Your browser on its own can display web pages, but extensions connect it to extra services: @@ -42,7 +53,7 @@ Without extensions, your browser is still useful, but with them, it becomes a po --- -Power cable connecting with bright electrical spark surrounded by floating tech icons representing MCP server connections +Power cable connecting with bright electrical spark surrounded by floating tech icons representing MCP server connections # Quick Start: MCP in 30 Seconds @@ -80,7 +91,7 @@ MCP Servers:
๐ŸŽฌ See it in action! -![MCP Status Demo](images/mcp-status-demo.gif) +![MCP Status Demo](assets/mcp-status-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -117,15 +128,31 @@ MCP makes Copilot aware of your actual development environment. # Configuring MCP Servers -Hands adjusting knobs and sliders on a professional audio mixing board representing MCP server configuration +Hands adjusting knobs and sliders on a professional audio mixing board representing MCP server configuration + +Now that you've seen MCP in action, let's set up additional servers. You can add servers in two ways: **from the built-in registry** (easiest โ€” guided setup right in the CLI) or by **editing the config file** manually (more flexible). Start with the registry option if you're not sure which to choose. + +--- + +## Installing MCP Servers from the Registry + +The CLI has a built-in MCP server registry that lets you discover and install popular servers with a guided setup โ€” no JSON editing required. + +```bash +copilot + +> /mcp search +``` + +Copilot opens an interactive picker showing available servers. Select one, and the CLI walks you through any required configuration (API keys, paths, etc.) and adds it to your config automatically. -Now that you've seen MCP in action, let's set up additional servers. This section covers the configuration file format and how to add new servers. +> ๐Ÿ’ก **Why use the registry?** It's the easiest way to get started โ€” you don't need to know the npm package name, command arguments, or JSON structure. The CLI handles all of that for you. --- ## MCP Configuration File -MCP servers are configured in `~/.copilot/mcp-config.json` (user-level, applies to all projects) or `.mcp.json` (project-level, placed in the root of your project). +MCP servers can be configured at the user level in `~/.copilot/mcp-config.json`, which applies across projects, at the project level in `.mcp.json`, or in the workspace config file `.github/mcp.json`. `.github/mcp.json` is auto-loaded alongside `.mcp.json`. If you used `/mcp search`, the CLI created or updated your user-level `~/.copilot/mcp-config.json`, but understanding the JSON format is useful when you want to customize or share project-level MCP configuration. > โš ๏ธ **Note**: `.vscode/mcp.json` is no longer supported as an MCP config source. If you have an existing `.vscode/mcp.json`, migrate it to `.mcp.json` in your project root. The CLI will show a migration hint if it detects an old config file. @@ -343,7 +370,7 @@ Save this as `~/.copilot/mcp-config.json` for global access or `.mcp.json` in th Now that you have MCP servers configured, let's see what they can do. -Using MCP Servers - Hub-and-spoke diagram showing a Developer CLI connected to GitHub, Filesystem, Context7, and Custom/Web Fetch servers +Using MCP Servers - Hub-and-spoke diagram showing a Developer CLI connected to GitHub, Filesystem, Context7, and Custom/Web Fetch servers --- @@ -541,7 +568,7 @@ copilot These workflows show why developers say "I never want to work without this again." Each example combines multiple MCP servers in a single session. -Issue to PR Workflow using MCP - Shows the complete flow from getting a GitHub issue through creating a pull request +Issue to PR Workflow using MCP - Shows the complete flow from getting a GitHub issue through creating a pull request *Complete MCP workflow: GitHub MCP retrieves repo data, Filesystem MCP finds code, Context7 MCP provides best practices, and Copilot handles analysis* @@ -601,7 +628,7 @@ Suggestions:
๐ŸŽฌ See the MCP workflow in action! -![MCP Workflow Demo](images/mcp-workflow-demo.gif) +![MCP Workflow Demo](assets/mcp-workflow-demo.gif) *Demo output varies. Your model, tools, and responses will differ from what's shown here.* @@ -693,7 +720,7 @@ Recommendations: # Practice -Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice +Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice **๐ŸŽ‰ You now know the essentials!** You understand MCP, you've seen how to configure servers, and you've seen real workflows in action. Now it's time to try it yourself. @@ -897,6 +924,7 @@ These work when you're already inside `copilot`: | Command | What It Does | |---------|--------------| | `/mcp show` | Show all configured MCP servers and their status | +| `/mcp list` | Show currently attached MCP servers and their status; can be run while Copilot is working | | `/mcp add` | Interactive setup for adding a new server | | `/mcp edit ` | Edit an existing server configuration | | `/mcp enable ` | Enable a disabled server (persists across sessions) | diff --git a/06-mcp-servers/images/browser-extensions-analogy.png b/06-mcp-servers/assets/browser-extensions-analogy.png similarity index 100% rename from 06-mcp-servers/images/browser-extensions-analogy.png rename to 06-mcp-servers/assets/browser-extensions-analogy.png diff --git a/06-mcp-servers/images/chapter-header.png b/06-mcp-servers/assets/chapter-header.png similarity index 100% rename from 06-mcp-servers/images/chapter-header.png rename to 06-mcp-servers/assets/chapter-header.png diff --git a/06-mcp-servers/images/configuring-mcp-servers.png b/06-mcp-servers/assets/configuring-mcp-servers.png similarity index 100% rename from 06-mcp-servers/images/configuring-mcp-servers.png rename to 06-mcp-servers/assets/configuring-mcp-servers.png diff --git a/06-mcp-servers/images/issue-to-pr-workflow.png b/06-mcp-servers/assets/issue-to-pr-workflow.png similarity index 100% rename from 06-mcp-servers/images/issue-to-pr-workflow.png rename to 06-mcp-servers/assets/issue-to-pr-workflow.png diff --git a/06-mcp-servers/images/mcp-status-demo.gif b/06-mcp-servers/assets/mcp-status-demo.gif similarity index 100% rename from 06-mcp-servers/images/mcp-status-demo.gif rename to 06-mcp-servers/assets/mcp-status-demo.gif diff --git a/06-mcp-servers/images/mcp-status-demo.tape b/06-mcp-servers/assets/mcp-status-demo.tape similarity index 100% rename from 06-mcp-servers/images/mcp-status-demo.tape rename to 06-mcp-servers/assets/mcp-status-demo.tape diff --git a/06-mcp-servers/images/mcp-workflow-demo.gif b/06-mcp-servers/assets/mcp-workflow-demo.gif similarity index 100% rename from 06-mcp-servers/images/mcp-workflow-demo.gif rename to 06-mcp-servers/assets/mcp-workflow-demo.gif diff --git a/06-mcp-servers/images/mcp-workflow-demo.tape b/06-mcp-servers/assets/mcp-workflow-demo.tape similarity index 100% rename from 06-mcp-servers/images/mcp-workflow-demo.tape rename to 06-mcp-servers/assets/mcp-workflow-demo.tape diff --git a/06-mcp-servers/images/multi-server-workflow.png b/06-mcp-servers/assets/multi-server-workflow.png similarity index 100% rename from 06-mcp-servers/images/multi-server-workflow.png rename to 06-mcp-servers/assets/multi-server-workflow.png diff --git a/06-mcp-servers/images/quick-start-mcp.png b/06-mcp-servers/assets/quick-start-mcp.png similarity index 100% rename from 06-mcp-servers/images/quick-start-mcp.png rename to 06-mcp-servers/assets/quick-start-mcp.png diff --git a/06-mcp-servers/images/using-mcp-servers.png b/06-mcp-servers/assets/using-mcp-servers.png similarity index 100% rename from 06-mcp-servers/images/using-mcp-servers.png rename to 06-mcp-servers/assets/using-mcp-servers.png diff --git a/06-mcp-servers/mcp-custom-server.md b/06-mcp-servers/mcp-custom-server.md index 03166f6a..beb96668 100644 --- a/06-mcp-servers/mcp-custom-server.md +++ b/06-mcp-servers/mcp-custom-server.md @@ -1,3 +1,14 @@ + + # Building a Custom MCP Server > โš ๏ธ **This content is completely optional.** You can be highly productive with Copilot CLI using only the pre-built MCP servers (GitHub, filesystem, Context7). This guide is for developers who want to connect Copilot to custom internal APIs. See the [MCP for Beginners course](https://github.com/microsoft/mcp-for-beginners) for more details. diff --git a/07-putting-it-together/README.md b/07-putting-it-together/README.md index 2678d647..829c7999 100644 --- a/07-putting-it-together/README.md +++ b/07-putting-it-together/README.md @@ -1,4 +1,15 @@ -![Chapter 07: Putting It All Together](images/chapter-header.png) + + +![Chapter 07: Putting It All Together](assets/chapter-header.png) > **Everything you learned combines here. Go from idea to merged PR in a single session.** @@ -21,7 +32,7 @@ By the end of this chapter, you'll be able to: ## ๐Ÿงฉ Real-World Analogy: The Orchestra -Orchestra Analogy - Unified Workflow +Orchestra Analogy - Unified Workflow A symphony orchestra has many sections: - **Strings** provide the foundation (like your core workflows) @@ -109,7 +120,7 @@ copilot # Additional Workflows -People assembling a colorful giant jigsaw puzzle with gears, representing how agents, skills, and MCP combine into unified workflows +People assembling a colorful giant jigsaw puzzle with gears, representing how agents, skills, and MCP combine into unified workflows For power users who completed Chapters 04-06, these workflows show how agents, skills, and MCP multiply your effectiveness. @@ -117,7 +128,7 @@ For power users who completed Chapters 04-06, these workflows show how agents, s Here's the mental model for combining everything: -The Integration Pattern - A 4-phase workflow: Gather Context (MCP), Analyze and Plan (Agents), Execute (Skills + Manual), Complete (MCP) +The Integration Pattern - A 4-phase workflow: Gather Context (MCP), Analyze and Plan (Agents), Execute (Skills + Manual), Complete (MCP) --- @@ -375,7 +386,7 @@ For teams with existing CI/CD pipelines, you can automate Copilot reviews on eve # Practice -Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice +Warm desk setup with monitor showing code, lamp, coffee cup, and headphones ready for hands-on practice Put the complete workflow into practice. diff --git a/07-putting-it-together/images/chapter-header.png b/07-putting-it-together/assets/chapter-header.png similarity index 100% rename from 07-putting-it-together/images/chapter-header.png rename to 07-putting-it-together/assets/chapter-header.png diff --git a/07-putting-it-together/images/combined-workflows.png b/07-putting-it-together/assets/combined-workflows.png similarity index 100% rename from 07-putting-it-together/images/combined-workflows.png rename to 07-putting-it-together/assets/combined-workflows.png diff --git a/07-putting-it-together/images/integration-pattern.png b/07-putting-it-together/assets/integration-pattern.png similarity index 100% rename from 07-putting-it-together/images/integration-pattern.png rename to 07-putting-it-together/assets/integration-pattern.png diff --git a/07-putting-it-together/images/orchestra-analogy.png b/07-putting-it-together/assets/orchestra-analogy.png similarity index 100% rename from 07-putting-it-together/images/orchestra-analogy.png rename to 07-putting-it-together/assets/orchestra-analogy.png diff --git a/GLOSSARY.md b/GLOSSARY.md index e1da765c..72fff8bb 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -76,6 +76,12 @@ Model Context Protocol. A standard for connecting AI assistants to external data --- +### Memory (Copilot CLI) + +A feature that lets Copilot CLI remember facts and preferences *across all sessions*, not just within a single one. Unlike session history (which saves a specific conversation), memory persists globally and is applied automatically in future sessions. Managed with the `/memory` slash command (`/memory on`, `/memory off`, `/memory show`). Memory can be scoped to your user account (visible across all repositories) or to a specific repository (shared with collaborators). + +--- + ## N ### npx diff --git a/README.md b/README.md index ffd5ed26..d6b8433d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,15 @@ -![GitHub Copilot CLI for Beginners](./images/copilot-banner.png) + + +![GitHub Copilot CLI for Beginners](./assets/copilot-banner.png) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)  [![Open project in GitHub Codespaces](https://img.shields.io/badge/Codespaces-Open-blue?style=flat-square&logo=github)](https://codespaces.new/github/copilot-cli-for-beginners?hide_repo_select=true&ref=main&quickstart=true)  @@ -23,12 +34,6 @@ This course is designed for: - **Terminal users** who prefer keyboard-driven workflows over IDE integrations - **Teams looking to standardize** AI-assisted code review and development practices - - - GitHub Copilot Dev Days - Find or host an event - - - ## ๐ŸŽฏ What You'll Learn This hands-on course takes you from zero to productive with GitHub Copilot CLI. You'll work with a single Python book collection app throughout all chapters, progressively improving it using AI-assisted workflows. By the end, you'll confidently use AI to review code, generate tests, debug issues, and automate workflows: all from your terminal. @@ -60,7 +65,7 @@ This course focuses on **GitHub Copilot CLI**, bringing AI assistance directly t ## ๐Ÿ“š Course Structure -![GitHub Copilot CLI Learning Path](images/learning-path.png) +![GitHub Copilot CLI Learning Path](assets/learning-path.png) | Chapter | Title | What You'll Build | |:-------:|-------|-------------------| @@ -92,10 +97,19 @@ The **[GitHub Copilot CLI command reference](https://docs.github.com/en/copilot/ ## ๐Ÿ™‹ Getting Help - ๐Ÿ› **Found a bug?** [Open an Issue](https://github.com/github/copilot-cli-for-beginners/issues) -- ๐Ÿค **Want to contribute?** PRs welcome! - ๐Ÿ“š **Official Docs:** [GitHub Copilot CLI Documentation](https://docs.github.com/copilot/concepts/agents/about-copilot-cli) +## Contributing + +> **Note**: The code used in the course is designed to generate specific types of output during reviews, explanations, and debugging so we aren't able to accept PRs that change the existing code. + +**How to contribute:** + +1. Fork this repository and clone it to your machine +2. Create a feature branch (`git checkout -b my-improvement`) +3. Make your changes +4. Submit a pull request + ## License This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](./LICENSE) file for the full terms. - diff --git a/appendices/README.md b/appendices/README.md index 30b93f2c..0e016943 100644 --- a/appendices/README.md +++ b/appendices/README.md @@ -1,3 +1,14 @@ + + # Appendices These appendices cover additional topics that extend the core course content. They're optional reading for when you need these specific capabilities. diff --git a/appendices/additional-context.md b/appendices/additional-context.md index 55906f74..6f13e377 100644 --- a/appendices/additional-context.md +++ b/appendices/additional-context.md @@ -1,3 +1,14 @@ + + # Additional Context Features > ๐Ÿ“– **Prerequisite**: Complete [Chapter 02: Context and Conversations](../02-context-conversations/README.md) before reading this appendix. diff --git a/appendices/ci-cd-integration.md b/appendices/ci-cd-integration.md index 176bd717..921e2957 100644 --- a/appendices/ci-cd-integration.md +++ b/appendices/ci-cd-integration.md @@ -1,3 +1,14 @@ + + # CI/CD Integration > ๐Ÿ“– **Prerequisite**: Complete [Chapter 07: Putting It All Together](../07-putting-it-together/README.md) before reading this appendix. diff --git a/images/chapter-header-bg.png b/assets/chapter-header-bg.png similarity index 100% rename from images/chapter-header-bg.png rename to assets/chapter-header-bg.png diff --git a/images/copilot-banner.png b/assets/copilot-banner.png similarity index 100% rename from images/copilot-banner.png rename to assets/copilot-banner.png diff --git a/images/copilot-dev-days.png b/assets/copilot-dev-days.png similarity index 100% rename from images/copilot-dev-days.png rename to assets/copilot-dev-days.png diff --git a/images/github-skills-logo.png b/assets/github-skills-logo.png similarity index 100% rename from images/github-skills-logo.png rename to assets/github-skills-logo.png diff --git a/images/learning-path.png b/assets/learning-path.png similarity index 100% rename from images/learning-path.png rename to assets/learning-path.png diff --git a/images/mockup.png b/assets/mockup.png similarity index 100% rename from images/mockup.png rename to assets/mockup.png diff --git a/images/practice.png b/assets/practice.png similarity index 100% rename from images/practice.png rename to assets/practice.png diff --git a/images/qr.png b/assets/qr.png similarity index 100% rename from images/qr.png rename to assets/qr.png diff --git a/images/quick-reference-header.png b/assets/quick-reference-header.png similarity index 100% rename from images/quick-reference-header.png rename to assets/quick-reference-header.png diff --git a/images/screenshot.png b/assets/screenshot.png similarity index 100% rename from images/screenshot.png rename to assets/screenshot.png diff --git a/package.json b/package.json index 26a84cf4..10eff8eb 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "create:tapes": "node .github/scripts/create-tapes.js", "generate:vhs": "node .github/scripts/generate-demos.js", "verify:gifs": "node .github/scripts/verify-gifs.js", + "copy:content-engine": "node .github/scripts/copy-content-engine-files.js", "generate:demos": "npm run create:tapes && npm run generate:vhs && npm run verify:gifs", "release": "npm run generate:demos", "release:ci": "npm run generate:headers"