From eee9654592c204c5ffbe43368bdcc02d8c36a926 Mon Sep 17 00:00:00 2001 From: hubwriter Date: Mon, 24 Nov 2025 09:53:42 +0000 Subject: [PATCH] Add new tutorial on burning down tech debt (#58552) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Kevin Heis Co-authored-by: Vanessa --- content/copilot/tutorials/index.md | 1 + .../tutorials/reduce-technical-debt.md | 381 ++++++++++++++++++ .../contributing/content-linter-rules.md | 2 +- .../linting-rules/frontmatter-hero-image.ts | 28 +- .../tests/unit/frontmatter-hero-image.ts | 51 ++- 5 files changed, 438 insertions(+), 25 deletions(-) create mode 100644 content/copilot/tutorials/reduce-technical-debt.md diff --git a/content/copilot/tutorials/index.md b/content/copilot/tutorials/index.md index 1bbc7b8bd6..0e4ebdf44c 100644 --- a/content/copilot/tutorials/index.md +++ b/content/copilot/tutorials/index.md @@ -21,6 +21,7 @@ children: - /explore-pull-requests - /write-tests - /refactor-code + - /reduce-technical-debt - /review-ai-generated-code - /learn-a-new-language - /modernize-legacy-code diff --git a/content/copilot/tutorials/reduce-technical-debt.md b/content/copilot/tutorials/reduce-technical-debt.md new file mode 100644 index 0000000000..eb2492948f --- /dev/null +++ b/content/copilot/tutorials/reduce-technical-debt.md @@ -0,0 +1,381 @@ +--- +title: Using GitHub Copilot to reduce technical debt +shortTitle: Reduce technical debt +intro: 'Use {% data variables.product.prodname_copilot_short %} to automate refactoring and maintenance tasks, freeing your team to focus on feature development.' +versions: + feature: copilot +topics: + - Copilot +contentType: tutorials +category: + - Burn down tech debt + - Author and optimize with Copilot +--- + +## Introduction + +Technical debt accumulates in every codebase: duplicate code, missing tests, outdated dependencies, and inconsistent patterns. These issues can accumulate because feature development is typically given a higher priority. This tutorial explains how you can use {% data variables.product.prodname_copilot %} to tackle technical debt systematically, without sacrificing feature velocity. + +### Who this tutorial is for + +This tutorial is designed to help engineering teams and technical leads reduce technical debt while maintaining the pace at which new features are delivered. You should have: + +* A {% data variables.product.prodname_copilot_short %} subscription with access to {% data variables.copilot.copilot_coding_agent %} +* Admin access to at least one repository +* Familiarity with your team's development workflow + +### What you'll accomplish + +By the end of this tutorial, you'll have learned about: + +* Using {% data variables.product.prodname_copilot_short %} to implement in-the-moment fixes +* Leveraging {% data variables.copilot.copilot_coding_agent %} for large-scale cleanup tasks +* Creating custom instructions to align {% data variables.product.prodname_copilot_short %} with your team's standards +* Measuring the impact of {% data variables.product.prodname_copilot_short %} on your technical debt + +## Understanding the technical debt problem + +Before starting to reduce the technical debt in a codebase, you should take some time to identify the types of technical debt your team faces most often. + +Common types of technical debt include: + +* **Code duplication** - The same logic implemented in multiple places +* **Missing tests** - Features without adequate test coverage +* **Outdated dependencies** - Libraries several versions behind current releases +* **Inconsistent patterns** - Different approaches to the same problem across your codebase +* **Legacy code** - Old code that works but doesn't follow current standards + +The cost of technical debt compounds over time: + +* Senior engineers spend time on routine updates instead of architecture design +* Code reviews become longer as reviewers debate inconsistent patterns +* New developers take longer to onboard due to confusing code organization +* Deployment risk increases as outdated dependencies accumulate vulnerabilities + +## Using {% data variables.product.prodname_copilot_short %} in your IDE for in-the-moment fixes + +The best way to avoid technical debt accumulating in your codebase is to prevent it getting into the codebase in the first place. + +When you encounter technical debt during development, fix it immediately using {% data variables.product.prodname_copilot_short %} in your IDE. + +### Quick refactoring workflow + +1. While working in your IDE, highlight code that needs improvement. +1. Open {% data variables.copilot.copilot_chat_short %} in the IDE. +1. Ask {% data variables.product.prodname_copilot_short %} to refactor the code. For example: + + * `Extract this into a reusable helper and add error handling` + * `Standardize this logging format to match our pattern` + * `Add null checks for all optional parameters` + * `Replace this deprecated API call with the current version` + +1. Review the suggested changes. +1. Accept the changes or ask {% data variables.product.prodname_copilot_short %} to modify its approach. +1. Run your tests to verify the changes work correctly. + +### Example: Standardizing error handling + +If you find inconsistent error handling—for example: + +```javascript id="err-handling" +// Highlight this code +try { + await fetchData(); +} catch (e) { + console.log(e); +} +``` + +Ask {% data variables.product.prodname_copilot_short %} to improve the code—for example: + +```copilot prompt copy ref="err-handling" +Refactor this to use structured logging and proper error handling +``` + +{% data variables.product.prodname_copilot_short %} might suggest: + +```javascript +try { + await fetchData(); +} catch (error) { + logger.error('Failed to fetch data', { + error: error.message, + stack: error.stack, + timestamp: new Date().toISOString() + }); + throw error; +} +``` + +> [!NOTE] This response is an example. {% data variables.copilot.copilot_chat_short %} responses are non-deterministic, so you may get a different response if you run the same prompt against the same code. + +By adopting the in-the-moment fix approach, you help to ensure that substandard code does not get added to your codebase, and you avoid the creation of a backlog issue that may never be addressed. + +For more details on using {% data variables.product.prodname_copilot_short %} in your IDE, see [AUTOTITLE](/copilot/using-github-copilot/asking-github-copilot-questions-in-your-ide). + +## Using {% data variables.copilot.copilot_coding_agent %} for large-scale refactoring + +Some refactoring tasks are just too big to complete while everyone on the team is busy developing new features. In this situation you can use {% data variables.copilot.copilot_coding_agent %} to handle these tasks autonomously. Human effort will still be required—at a minimum for reviewing the changes {% data variables.copilot.copilot_coding_agent %} proposes—but getting {% data variables.product.prodname_copilot_short %} to do the bulk of the work can allow you to carry out large-scale refactoring with much less impact on your team's productivity. + +### When to use {% data variables.copilot.copilot_coding_agent %} + +Use {% data variables.copilot.copilot_coding_agent %} for tasks that: + +* Touch many files across your codebase +* Require systematic changes (like removing old feature flags) +* Need careful testing but are straightforward to implement +* Would interrupt feature development if done manually + +Examples include: + +* Framework upgrades that affect 50+ files +* Removing deprecated feature flags +* Migrating to strict TypeScript +* Updating dependency versions +* Standardizing import patterns + +### Workflow for {% data variables.copilot.copilot_coding_agent %} + +1. Create a {% data variables.product.prodname_dotcom %} issue describing the refactoring task. + + Be specific about what needs to change. For example: + + ```markdown + Remove all feature flags marked for cleanup in Q2. + + These flags are: + - `enable_new_dashboard` + - `beta_export_feature` + - `experimental_search` + + All three flags are enabled by default in production. + + Remove the flag checks and keep the "enabled" code path. + ``` + +1. Assign the issue to the **Copilot** user. +1. {% data variables.copilot.copilot_coding_agent %} will: + + * Set up a development environment + * Open a draft pull request + * Make the required changes to the code + * Run your tests + * Finalize the pull request for review + * Request your review of the pull request + +1. Review the pull request just as you would a pull request raised by a human. +1. Leave comments if changes are needed—{% data variables.copilot.copilot_coding_agent %} will update the pull request based on your feedback. +1. Iterate in this way until the work is completed correctly. +1. Approve and merge the pull request. + +For more information, see [AUTOTITLE](/copilot/how-tos/use-copilot-agents/coding-agent/create-a-pr#assigning-an-issue-to-copilot) and [AUTOTITLE](/copilot/how-tos/use-copilot-agents/coding-agent/review-copilot-prs). + +### Safety guardrails + +{% data variables.copilot.copilot_coding_agent %} operates with built-in safety measures: + +* It can only push to its own `copilot/*` branches +* It cannot merge pull requests—requires your approval +* All commits are logged and auditable +* Your existing branch protections remain active +* CI/CD checks run before any code is merged + +## Creating custom instructions for your team + +Custom instructions help {% data variables.product.prodname_copilot_short %} understand your team's coding standards and patterns. This ensures suggestions match your expectations from the start. + +### Setting up custom instructions + +1. In your repository, create a file named `.github/copilot-instructions.md`. +1. Add your team's coding standards in clear, straightforward statements—for example, using bulleted lists. +1. Commit the file to your repository. + +### Example custom instructions + +Here's an example of effective custom instructions: + +```markdown +## Our Standards + +- Use structured logging, not console.log +- Sanitize user input before database queries +- Check for null/undefined on all optional parameters +- Keep functions under 50 lines (extract helpers if needed) +- Every public function needs a test +- Flag any loops that might trigger N+1 queries + +## Error Handling + +- Always use try-catch blocks for async operations +- Log errors with context (user ID, request ID, timestamp) +- Never swallow errors silently +- Return appropriate HTTP status codes + +## Testing Requirements + +- Unit tests for all business logic +- Integration tests for API endpoints +- Mock external services in tests +- Test both success and failure paths +``` + +For detailed guidance on writing custom instructions, see [AUTOTITLE](/copilot/customizing-copilot/adding-custom-instructions-for-github-copilot). + +### Benefits of custom instructions + +With custom instructions in place: + +* {% data variables.product.prodname_copilot_short %} suggests code following your patterns +* Code reviews become faster, with fewer discussions about style changes +* New team members learn your standards through {% data variables.product.prodname_copilot_short %} suggestions +* Consistency improves across your codebase + +## Running a pilot program + +Start small to validate {% data variables.product.prodname_copilot_short %}'s impact on your technical debt before rolling it out widely. + +### Week 1: Set up and establish baselines + +1. Ensure all pilot participants have {% data variables.product.prodname_copilot_short %} access with {% data variables.copilot.copilot_coding_agent %} enabled. +1. Count the technical debt items in your backlog: + + * Number of "tech debt", "chore", or similar labeled issues + * Number of outdated dependencies + * Number of files failing linter checks + +1. Track current metrics: + + * Average time from pull request creation to merge for refactoring PRs + * Average number of review rounds per refactoring PR + +1. Create your first `.github/copilot-instructions.md` file with 3–5 of your most important standards. + +### Weeks 2–4: Run the pilot + +1. Select 5–10 repositories for your pilot. +1. Choose 1–2 specific problems to address. For example: + + * Code duplication in a particular area + * Missing tests on frequently changed files + * Outdated dependencies + +1. Use {% data variables.product.prodname_copilot_short %} in your IDE for quick fixes as you encounter issues. +1. Assign larger cleanup tasks to {% data variables.copilot.copilot_coding_agent %}. +1. Review all {% data variables.product.prodname_copilot_short %}-generated PRs carefully. +1. Provide feedback on suggestions to help {% data variables.product.prodname_copilot_short %} learn your preferences. + +### Week 5: Evaluate results + +After the pilot, measure your results: + +* How much faster are refactoring pull requests getting merged? +* How many review rounds do they require now? +* Which types of code change suggestions, made by {% data variables.copilot.copilot_coding_agent %} in pull requests, did developers accept most often? +* Which suggestions needed the most revision? +* Are your technical debt metrics improving? + + * Linter warnings decreasing? + * Test coverage increasing? + * Dependency versions more current? + +Update your custom instructions based on what you learned about which guidance helped {% data variables.product.prodname_copilot_short %} most. + +## Measuring success + +Track specific metrics to understand {% data variables.product.prodname_copilot_short %}'s impact on your technical debt. + +### Velocity metrics + +Monitor how {% data variables.product.prodname_copilot_short %} affects development speed: + +* Time to close technical debt issues (target: 30–50% reduction) +* Number of technical debt pull requests merged per week (target: 2–3x increase) +* Average number of review cycles per refactoring pull request (assess whether this increased or decreased) + +### Quality metrics + +Ensure quality improves alongside velocity: + +* Linter warning count (this should trend downward) +* Test coverage percentage (this should trend upward) +* Number of production incidents related to refactored code (assess whether this changed) + +### Engineer satisfaction + +Survey your team regularly: + +* Are engineers spending less time on routine maintenance? +* Are code reviews focusing more on architecture and less on style? +* Is onboarding faster for new team members? + +## Troubleshooting + +### {% data variables.product.prodname_copilot_short %} suggests incorrect changes + +If {% data variables.product.prodname_copilot_short %} consistently suggests code that doesn't match your needs: + +* Review your custom instructions—they may be too vague or contradictory +* Provide more specific context in your prompts +* Add examples of good code to your custom instructions +* Leave detailed feedback in pull request reviews to allow {% data variables.copilot.copilot_coding_agent %} to fix the problems + +### Pull requests are too large to review + +If {% data variables.copilot.copilot_coding_agent %} creates pull requests that are difficult to review: + +* Break large tasks into smaller, focused issues +* Ask {% data variables.copilot.copilot_coding_agent %} to handle one file or directory at a time +* Use more specific issue descriptions + +### Changes break tests + +If refactoring introduces test failures: + +* Ensure your test suite runs reliably before using {% data variables.copilot.copilot_coding_agent %} +* Review {% data variables.product.prodname_copilot_short %} changes carefully before merging +* Ask {% data variables.product.prodname_copilot_short %} to update tests along with the code changes + +### Team adoption is slow + +If your team isn't using {% data variables.product.prodname_copilot_short %} for technical debt: + +* Share success stories from early adopters +* Demonstrate time savings in team meetings +* Start with the most annoying technical debt items +* Make creating custom instructions a team activity + +## Conclusion + +In this tutorial, you learned how to use {% data variables.product.prodname_copilot_short %} to systematically reduce technical debt. You now know how to: + +* Fix technical debt immediately using {% data variables.product.prodname_copilot_short %} in your IDE +* Assign large refactoring tasks to {% data variables.copilot.copilot_coding_agent %} +* Create custom instructions that align {% data variables.product.prodname_copilot_short %} with your team's standards +* Run a pilot program to validate the approach +* Measure {% data variables.product.prodname_copilot_short %}'s impact on technical debt + +By automating routine refactoring and maintenance tasks, {% data variables.product.prodname_copilot_short %} frees you to focus on architecture, feature development, and other high-value work. + +### Quick survey + +{% note %} + +After reading this tutorial, do you feel confident you can use {% data variables.product.prodname_copilot_short %} to reduce the technical debt in a codebase? + +Yes No + +{% endnote %} + +## Next steps + +* **Expand your pilot**: Roll out to more repositories based on your pilot results. +* **Automate dependency updates**: Create recurring issues for {% data variables.copilot.copilot_coding_agent %} to handle dependency updates. +* **Build a refactoring queue**: Label issues in your backlog as good for {% data variables.product.prodname_copilot_short %} then regularly assign a batch of these to {% data variables.product.prodname_copilot_short %} to work on. +* **Share best practices**: Document successful prompts and custom instructions for your team. + +## Further reading + +* [AUTOTITLE](/copilot/using-github-copilot/coding-agent) +* [AUTOTITLE](/copilot/tutorials/refactoring-code-with-github-copilot) +* [How to use GitHub Copilot in your IDE: Tips, tricks, and best practices](https://github.blog/developer-skills/github/how-to-use-github-copilot-in-your-ide-tips-tricks-and-best-practices/) in the {% data variables.product.company_short %} blog +* [5 ways to integrate GitHub Copilot coding agent into your workflow](https://github.blog/ai-and-ml/github-copilot/5-ways-to-integrate-github-copilot-coding-agent-into-your-workflow/) in the {% data variables.product.company_short %} blog diff --git a/data/reusables/contributing/content-linter-rules.md b/data/reusables/contributing/content-linter-rules.md index 68246a5a50..11010f9ce8 100644 --- a/data/reusables/contributing/content-linter-rules.md +++ b/data/reusables/contributing/content-linter-rules.md @@ -62,7 +62,7 @@ | GHD058 | journey-tracks-liquid | Journey track properties must use valid Liquid syntax | error | frontmatter, journey-tracks, liquid | | GHD059 | journey-tracks-guide-path-exists | Journey track guide paths must reference existing content files | error | frontmatter, journey-tracks | | GHD060 | journey-tracks-unique-ids | Journey track IDs must be unique within a page | error | frontmatter, journey-tracks, unique-ids | -| GHD061 | frontmatter-hero-image | Hero image paths must be absolute and point to valid images in /assets/images/banner-images/ | error | frontmatter, images | +| GHD061 | frontmatter-hero-image | Hero image paths must be absolute, extensionless, and point to valid images in /assets/images/banner-images/ | error | frontmatter, images | | GHD062 | frontmatter-intro-links | introLinks keys must be valid keys defined in data/ui.yml under product_landing | error | frontmatter, single-source | | [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: octicon- | The octicon liquid syntax used is deprecated. Use this format instead `octicon "" aria-label=""` | error | | | [search-replace](https://github.com/OnkarRuikar/markdownlint-rule-search-replace) | deprecated liquid syntax: site.data | Catch occurrences of deprecated liquid data syntax. | error | | diff --git a/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts b/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts index ba573884bb..6aa96af64d 100644 --- a/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts +++ b/src/content-linter/lib/linting-rules/frontmatter-hero-image.ts @@ -10,7 +10,7 @@ interface Frontmatter { [key: string]: any } -// Get the list of valid hero images +// Get the list of valid hero images (without extensions) function getValidHeroImages(): string[] { const ROOT = process.env.ROOT || '.' const heroImageDir = path.join(ROOT, 'assets/images/banner-images') @@ -21,8 +21,11 @@ function getValidHeroImages(): string[] { } const files = fs.readdirSync(heroImageDir) - // Return absolute paths as they would appear in frontmatter - return files.map((file) => `/assets/images/banner-images/${file}`) + // Return absolute paths without extensions as they should appear in frontmatter + return files.map((file) => { + const baseName = path.basename(file, path.extname(file)) + return `/assets/images/banner-images/${baseName}` + }) } catch { return [] } @@ -31,7 +34,7 @@ function getValidHeroImages(): string[] { export const frontmatterHeroImage: Rule = { names: ['GHD061', 'frontmatter-hero-image'], description: - 'Hero image paths must be absolute and point to valid images in /assets/images/banner-images/', + 'Hero image paths must be absolute, extensionless, and point to valid images in /assets/images/banner-images/', tags: ['frontmatter', 'images'], function: (params: RuleParams, onError: RuleErrorCallback) => { // Only check index.md files @@ -70,7 +73,22 @@ export const frontmatterHeroImage: Rule = { return } - // Check if the file actually exists + // Check if the path includes a file extension (which is not allowed) + if (path.extname(heroImage)) { + const line = params.lines.find((ln: string) => ln.trim().startsWith('heroImage:')) + const lineNumber = line ? params.lines.indexOf(line) + 1 : 1 + const withoutExtension = heroImage.substring(0, heroImage.lastIndexOf('.')) + addError( + onError, + lineNumber, + `Hero image path must not include file extension. Use: ${withoutExtension}`, + line || '', + null, // No fix possible + ) + return + } + + // Check if a file with this base name actually exists const validHeroImages = getValidHeroImages() if (validHeroImages.length > 0 && !validHeroImages.includes(heroImage)) { const line = params.lines.find((ln: string) => ln.trim().startsWith('heroImage:')) diff --git a/src/content-linter/tests/unit/frontmatter-hero-image.ts b/src/content-linter/tests/unit/frontmatter-hero-image.ts index ac3096c703..5130cbbd4e 100644 --- a/src/content-linter/tests/unit/frontmatter-hero-image.ts +++ b/src/content-linter/tests/unit/frontmatter-hero-image.ts @@ -6,7 +6,24 @@ import { frontmatterHeroImage } from '../../lib/linting-rules/frontmatter-hero-i const fmOptions = { markdownlintOptions: { frontMatter: null } } describe(frontmatterHeroImage.names.join(' - '), () => { - test('valid absolute heroImage path passes', async () => { + test('valid absolute extensionless heroImage path passes', async () => { + const markdown = [ + '---', + 'title: Test', + "heroImage: '/assets/images/banner-images/hero-1'", + '---', + '', + '# Test', + ].join('\n') + const result = await runRule(frontmatterHeroImage, { + strings: { 'content/test/index.md': markdown }, + ...fmOptions, + }) + const errors = result['content/test/index.md'] + expect(errors.length).toBe(0) + }) + + test('heroImage path with extension fails', async () => { const markdown = [ '---', 'title: Test', @@ -20,7 +37,8 @@ describe(frontmatterHeroImage.names.join(' - '), () => { ...fmOptions, }) const errors = result['content/test/index.md'] - expect(errors.length).toBe(0) + expect(errors.length).toBe(1) + expect(errors[0].errorDetail).toContain('must not include file extension') }) test('non-index.md file is ignored', async () => { @@ -51,14 +69,9 @@ describe(frontmatterHeroImage.names.join(' - '), () => { }) test('relative heroImage path fails', async () => { - const markdown = [ - '---', - 'title: Test', - "heroImage: 'images/hero-1.png'", - '---', - '', - '# Test', - ].join('\n') + const markdown = ['---', 'title: Test', "heroImage: 'images/hero-1'", '---', '', '# Test'].join( + '\n', + ) const result = await runRule(frontmatterHeroImage, { strings: { 'content/test/index.md': markdown }, ...fmOptions, @@ -72,7 +85,7 @@ describe(frontmatterHeroImage.names.join(' - '), () => { const markdown = [ '---', 'title: Test', - "heroImage: '/assets/images/other/hero-1.png'", + "heroImage: '/assets/images/other/hero-1'", '---', '', '# Test', @@ -90,7 +103,7 @@ describe(frontmatterHeroImage.names.join(' - '), () => { const markdown = [ '---', 'title: Test', - "heroImage: '/assets/images/banner-images/non-existent.png'", + "heroImage: '/assets/images/banner-images/non-existent'", '---', '', '# Test', @@ -105,14 +118,14 @@ describe(frontmatterHeroImage.names.join(' - '), () => { }) test('all valid hero images pass', async () => { - // Test each valid hero image + // Test each valid hero image (extensionless) const validImages = [ - "heroImage: '/assets/images/banner-images/hero-1.png'", - "heroImage: '/assets/images/banner-images/hero-2.png'", - "heroImage: '/assets/images/banner-images/hero-3.png'", - "heroImage: '/assets/images/banner-images/hero-4.png'", - "heroImage: '/assets/images/banner-images/hero-5.png'", - "heroImage: '/assets/images/banner-images/hero-6.png'", + "heroImage: '/assets/images/banner-images/hero-1'", + "heroImage: '/assets/images/banner-images/hero-2'", + "heroImage: '/assets/images/banner-images/hero-3'", + "heroImage: '/assets/images/banner-images/hero-4'", + "heroImage: '/assets/images/banner-images/hero-5'", + "heroImage: '/assets/images/banner-images/hero-6'", ] for (const heroImageLine of validImages) {