Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-03-03
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-03-03
Udostępnij Udostępnij Kontakt
Wprowadzenie

Przez dwanaście wpisów omawialiśmy mechanizmy – instrukcje, skills, agenci, MCP, hooks, workflows, plugins. Teraz czas zobaczyć jak to wszystko przekłada się na konkretną różnicę w kodzie który Copilot generuje. Bo w końcu to jest jedyna miara która ma znaczenie: czy output jest lepszy?

Ten wpis to zbiór przykładów przed i po. Dokumentacja oryginalna pokazuje scenariusze w TypeScript/Node.js. Mój główny wkład to przetłumaczenie tej logiki na .NET – bo to nasz domyślny wybór.

Osiem przykładów, każdy zorganizowany tak samo: prompt który zadałem, kod który Copilot wygenerował bez customizacji, kod który wygenerował po customizacji, i – najważniejsze – co konkretnie zmieniłem żeby to osiągnąć.

Oryginalna dokumentacja: Before/After Customization Examples – Awesome GitHub Copilot Learning Hub.

Przykłady ze świata .NET – HTTP Client

1
Wywołanie zewnętrznego API – HttpClient w serwisie
Prompt: "Napisz metodę pobierającą zamówienie z zewnętrznego API po ID"
Instrukcja
❌ Przed – bez customizacji
public async Task<Order> GetOrderAsync(int id)
{
    var client = new HttpClient();
    var url = $"https://api.example.com/orders/{id}";
    var response = await client.GetAsync(url);
    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<Order>(json)!;
}
  • Tworzy nowy HttpClient przy każdym wywołaniu – wyczerpuje sockety
  • Hardcodowany URL – powinien być w konfiguracji
  • Brak sprawdzenia kodu odpowiedzi HTTP
  • Brak obsługi błędów i logowania
  • Brak polityki retry dla przejściowych awarii
✅ Po – z instrukcją path-specific
public async Task<OrderDto> GetOrderAsync(
    int id,
    CancellationToken ct = default)
{
    try
    {
        var response = await _httpClient
            .GetAsync($"orders/{id}", ct);
        response.EnsureSuccessStatusCode();
        return await response.Content
            .ReadFromJsonAsync<OrderDto>(ct) ??
                throw new ApiException("Empty response");
    }
    catch (HttpRequestException ex)
    {
        _logger.LogError(ex,
            "Failed to fetch order {OrderId}", id);
        throw new ExternalServiceException(
            "Order service unavailable", ex);
    }
}
  • IHttpClientFactory wstrzyknięty przez DI – brak socket exhaustion
  • URL bazowy z konfiguracji, metoda używa relatywnego path
  • EnsureSuccessStatusCode() i obsługa wyjątku
  • Strukturalne logowanie z kontekstem (OrderId)
  • Polityka retry skonfigurowana w Program.cs przez Polly
🔧 Co dodałem – instrukcja path-specific dla serwisów HTTP
Plik .github/instructions/http-services.instructions.md z applyTo: "**/Infrastructure/**/*HttpClient.cs, **/Services/**/*ApiService.cs". Instrukcja zawiera: zakaz new HttpClient(), wymaganie IHttpClientFactory przez DI, bazowy URL z IConfiguration, EnsureSuccessStatusCode(), obsługa HttpRequestException, strukturalne logowanie i CancellationToken w każdej metodzie async.
0ręcznych poprawek
|
5→0problemów w kodzie
|
100%zgodność z konwencjami
2
Generowanie testów jednostkowych dla handlera MediatR
Prompt: "Napisz testy dla CancelOrderCommandHandler"
Skill
❌ Przed – bez skilla
public class CancelOrderCommandHandlerTests
{
    [Fact]
    public async Task Handle_ShouldCancelOrder()
    {
        var handler =
            new CancelOrderCommandHandler();
        var result = await handler.Handle(
            new CancelOrderCommand(1),
            CancellationToken.None);
        Assert.NotNull(result);
    }
}
  • Brak mocka repozytorium – test się nie skompiluje
  • Brak komentarzy AAA (Arrange/Act/Assert)
  • Jedna asercja Assert.NotNull – nie sprawdza zachowania
  • Brak testów edge case: zamówienie nie istnieje, już anulowane
  • Niezgodna konwencja nazewnictwa testów
✅ Po – ze skillem generate-unit-tests
public class CancelOrderCommandHandlerTests
{
    private readonly IOrderRepository _repo;
    private readonly CancelOrderCommandHandler _sut;

    public CancelOrderCommandHandlerTests()
    {
        _repo = Substitute.For<IOrderRepository>();
        _sut = new CancelOrderCommandHandler(_repo);
    }

    [Fact]
    public async Task Handle_WhenOrderExists_
        ShouldReturnSuccessAndCancelOrder()
    {
        // Arrange
        var order = new Order { Id = 1,
            Status = OrderStatus.Pending };
        _repo.GetByIdAsync(1).Returns(order);

        // Act
        var result = await _sut.Handle(
            new CancelOrderCommand(1), default);

        // Assert
        result.IsSuccess.Should().BeTrue();
        order.Status.Should().Be(
            OrderStatus.Cancelled);
    }

    [Fact]
    public async Task Handle_WhenOrderNotFound_
        ShouldReturnNotFound()
    {
        // Arrange
        _repo.GetByIdAsync(99).Returns((Order?)null);

        // Act
        var result = await _sut.Handle(
            new CancelOrderCommand(99), default);

        // Assert
        result.Status.Should().Be(
            ResultStatus.NotFound);
    }
}
  • NSubstitute w konstruktorze – wzorzec SUT z wpisu 6
  • Sekcje AAA z komentarzami w każdym teście
  • Nazwy metod: _WhenX_ShouldY
  • FluentAssertions zamiast Assert.NotNull
  • Pokrycie: happy path + not found + already cancelled
🔧 Co dodałem – skill generate-unit-tests (wpis 6)
Skill .github/skills/generate-unit-tests/SKILL.md z opisem: xUnit + FluentAssertions + NSubstitute, wzorzec AAA, konwencja nazewnictwa Method_WhenCondition_ShouldResult, zasada "testuj zachowanie nie implementację" i szablon klasy testowej w templates/test-class-template.cs.
1→3+scenariusze testowe
|
test kompiluje się od razu
|
0konieczności przepisywania
3
Nowy endpoint w Minimal API
Prompt: "Dodaj endpoint POST /orders/{id}/cancel"
Skill
❌ Przed – bez customizacji
app.MapPost("/orders/{id}/cancel",
    async (int id, AppDbContext db) =>
{
    var order = await db.Orders.FindAsync(id);
    if (order == null)
        return Results.NotFound();
    order.Status = "Cancelled";
    await db.SaveChangesAsync();
    return Results.Ok();
});
  • AppDbContext bezpośrednio w endpoincie – naruszenie Clean Architecture
  • Brak MediatR – logika biznesowa w warstwie API
  • Brak walidacji, autoryzacji i WithName()
  • Status jako string zamiast enum
  • Brak CancellationToken
✅ Po – ze skillem scaffold-api-endpoint
group.MapPost("/{id}/cancel",
    async (
        int id,
        ISender sender,
        CancellationToken ct) =>
{
    var result = await sender.Send(
        new CancelOrderCommand(id), ct);
    return result.ToMinimalApiResult();
})
.WithName("CancelOrder")
.WithSummary("Anuluje zamówienie")
.Produces(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status404NotFound)
.ProducesProblem(StatusCodes.Status409Conflict)
.RequireAuthorization();
  • MediatR ISender – logika w handlerze, endpoint tylko orkiestruje
  • Ardalis.Result → .ToMinimalApiResult() mapuje na HTTP status
  • .WithName(), .WithSummary() i OpenAPI responses
  • .RequireAuthorization() z automatu
  • CancellationToken propagowany do handlera
🔧 Co dodałem – skill scaffold-api-endpoint (wpis 6)
Skill z szablonami dla Command, Handler, Validator i Endpoint. W SKILL.md: zakaz bezpośredniego DbContext w API, wymaganie MediatR ISender, Ardalis.Result, wzorzec .ToMinimalApiResult(), metadane OpenAPI i RequireAuthorization() jako domyślne.
✓ CAClean Architecture zachowana
|
4+metadane OpenAPI z automatu
|
0naruszeń architektury
4
Migracja Entity Framework Core
Prompt: "Dodaj indeks na Orders.CustomerId i Orders.Status"
Skill
❌ Przed – bez customizacji
// Copilot sugeruje ręczne dodanie do OnModelCreating:
modelBuilder.Entity<Order>()
    .HasIndex(o => o.CustomerId);

modelBuilder.Entity<Order>()
    .HasIndex(o => o.Status);

// Brak komendy migracji, brak nazwy, brak
// indeksu złożonego, brak filtru IS NOT NULL
  • Brak komendy dotnet ef migrations add
  • Indeksy bez nazw konwencjonalnych (IX_ prefix)
  • Dwa osobne indeksy zamiast jednego złożonego (złe dla zapytań)
  • Brak filtrowanego indeksu dla częstego warunku WHERE
  • Nie sprawdza czy indeks już istnieje
✅ Po – ze skillem create-ef-migration
// 1. Najpierw weryfikacja przez MCP PostgreSQL:
// "Sprawdź czy IX_Orders_CustomerId_Status istnieje"
// → brak, można dodać

// 2. Polecenie migracji:
dotnet ef migrations add AddOrdersCustomerStatusIndex \
  --project src/MyApp.Infrastructure \
  --startup-project src/MyApp.API

// 3. Wygenerowana migracja:
migrationBuilder.CreateIndex(
    name: "IX_Orders_CustomerId_Status",
    table: "Orders",
    columns: new[] { "CustomerId", "Status" },
    filter: "\"Status\" != 'Completed'");

// 4. Checklist przed commitem (ze skilla):
// ✓ Indeks złożony zamiast dwóch osobnych
// ✓ Filtr wyklucza zakończone zamówienia
// ✓ Metoda Down() poprawna
// ✓ NULL handling sprawdzony
  • Weryfikacja przez MCP PostgreSQL czy indeks nie istnieje
  • Konwencjonalna nazwa IX_Table_Columns
  • Indeks złożony – pokrywa zapytania filtrujące po obu kolumnach
  • Filtrowany indeks – mniejszy, szybszy dla active orders
  • Gotowa komenda CLI z poprawnymi ścieżkami projektów
🔧 Co dodałem – skill create-ef-migration (wpis 6) + MCP PostgreSQL (wpis 8)
Skill z checklist przed commitem, konwencją nazewnictwa IX_, zasadą indeksów złożonych i komendami CLI. MCP PostgreSQL pozwala agentowi sprawdzić aktualny schemat bazy zanim zaproponuje migrację.
✓ MCPweryfikacja schematu live
|
2→1indeksy (złożony zamiast 2)
|
filtrowany indeks dla wydajności
5
Code review – komentarze do PR
Prompt: "Zreviewuj zmiany w OrderService.cs"
Agent
❌ Przed – Copilot bez agenta
// Typowa odpowiedź bez customizacji:

Kod wygląda dobrze. Kilka sugestii:
- Dodaj walidację dla parametrów wejściowych
- Rozważ dodanie logowania
- Metoda jest dość długa, rozważ refaktoryzację
- Pamiętaj o obsłudze wyjątków
  • Ogólniki bez konkretnych linii kodu
  • Brak priorytetyzacji (co blokuje merge, co sugestia)
  • Nie sprawdza naruszeń architektury projektu
  • Brak propozycji konkretnego kodu poprawki
  • Nie zna naszych konwencji (FluentValidation, Serilog)
✅ Po – z agentem dotnet-code-reviewer
## Podsumowanie zmian
Dodano metodę CancelOrder z podstawową logiką
biznesową. Zmiana dotyczy 3 plików w warstwie
Application.

## [BLOCKER] Brak walidacji wejścia
Plik: Application/Orders/OrderService.cs, l.47
Problem: CancelOrder przyjmuje orderId bez
weryfikacji - brakuje CancelOrderCommandValidator.
Sugestia:
public class CancelOrderCommandValidator
  : AbstractValidator<CancelOrderCommand> {
  public CancelOrderCommandValidator()
  { RuleFor(x => x.OrderId).GreaterThan(0); }
}

## [WARNING] Naruszenie CA – DbContext w Application
Plik: Application/Orders/OrderService.cs, l.12
Problem: AppDbContext wstrzyknięty bezpośrednio
zamiast IOrderRepository. Naruszenie zasady
dependency inversion.

## Ogólna ocena: REQUEST CHANGES
2 blokery, 1 warning. Po poprawkach: APPROVE.
  • Priorytetyzacja: BLOCKER / WARNING / INFO
  • Konkretne linie kodu z plikiem i numerem
  • Gotowy kod poprawki dla każdego problemu
  • Zna architekturę projektu (CA, IOrderRepository)
  • Finalna ocena: APPROVE / REQUEST CHANGES
🔧 Co dodałem – agent dotnet-code-reviewer (wpis 7)
Agent z precyzyjną ekspertyzą (.NET 9, Clean Architecture, CQRS), sekcją "Co sprawdzam" (architektura, konwencje, testy, security, wydajność), formatem komentarzy BLOCKER/WARNING/INFO z kodem poprawki i guardrailami ("nie modyfikuj plików, tylko czytaj i komentuj").
~2 minczas review (było 20-30)
|
✓ CAzna architekturę projektu
|
100%spójny format
6
Globalny error handler w ASP.NET Core
Prompt: "Dodaj globalną obsługę wyjątków do aplikacji"
Instrukcja
❌ Przed – bez customizacji
app.UseExceptionHandler(appError =>
{
  appError.Run(async context =>
  {
    context.Response.StatusCode = 500;
    await context.Response.WriteAsync(
      "An error occurred.");
  });
});
  • Zawsze status 500 – brak mapowania typów wyjątków
  • Odpowiedź jako plain text, nie RFC 7807 ProblemDetails
  • Brak logowania z pełnym kontekstem żądania
  • Nie rozróżnia środowisk (dev vs prod)
✅ Po – z globalną instrukcją + instrukcją dla middleware
public class GlobalExceptionHandler
    : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext ctx,
        Exception ex,
        CancellationToken ct)
    {
        var (status, title) = ex switch {
            NotFoundException => (404, "Not Found"),
            ValidationException => (400, "Bad Request"),
            UnauthorizedException =>(401, "Unauthorized"),
            _ => (500, "Internal Server Error")
        };
        _logger.LogError(ex,
            "Unhandled {ExceptionType} on {Path}",
            ex.GetType().Name, ctx.Request.Path);
        await Results.Problem(
            statusCode: status, title: title)
            .ExecuteAsync(ctx);
        return true;
    }
}
  • ASP.NET Core 8+ IExceptionHandler – rekomendowany wzorzec
  • Mapowanie typów domenowych wyjątków na kody HTTP
  • RFC 7807 ProblemDetails przez Results.Problem()
  • Strukturalne logowanie z typem i ścieżką żądania
🔧 Co dodałem – globalna instrukcja + instrukcja dla middleware
W copilot-instructions.md: zakaz plain-text error responses, wymóg RFC 7807 ProblemDetails, wymaganie IExceptionHandler zamiast UseExceptionHandler lambda. Instrukcja path-specific applyTo: "**/Middleware/**/*.cs" dodaje mapowanie typów wyjątków domenowych.
500→4statusy HTTP (poprawne mapowanie)
|
RFC 7807ProblemDetails z automatu
|
rekomendowany wzorzec .NET 8
7
Automatyczny quality gate po każdej sesji agenta
Scenariusz: Agent implementuje nową funkcję – czy kod przejdzie standardy bez Twojej interwencji?
Hook
❌ Przed – bez hooka
// Agent kończy implementację.
// Ty:
1. Otwierasz terminal
2. dotnet format --verify-no-changes
   → 3 pliki z błędami formatowania 🔴
3. dotnet build
   → 2 warning CS8600 (null reference) 🟡
4. dotnet test
   → 1 test failed (agent zmienił
     asercję zamiast naprawić kod) 🔴
5. Ręcznie poprawiasz → wraca do agenta
→ kolejna runda...
Łączny czas overhead: ~15 minut
  • Ręczne uruchamianie narzędzi po każdej sesji
  • Problemy wykryte dopiero przez Ciebie, nie agenta
  • Agent nie dostaje feedbacku – nie naprawia sam
  • Warningi kumulują się "na potem" bo są tylko żółte
✅ Po – z hookiem dotnet-quality-gate (wpis 9)
// Agent kończy implementację.
// Hook agentStop odpala się automatycznie:

╔══════════════════════════════════════╗
║ 🔬 .NET Quality Gate – Copilot Hook ║
╚══════════════════════════════════════╝

▶ [1/3] dotnet format --verify-no-changes
❌ 3 pliki z błędami formatowania
💡 Uruchom: dotnet format MyApp.sln

▶ [2/3] Roslyn Analyzers...
⚠️ 2 warning CS8600
(FAIL_ON_WARNINGS=true → traktuję jako błąd)

═══════════════════════════════════════
🚫 Quality gate NIEUDANY (2 problemy)
Agent zablokowany do naprawy błędów.
═══════════════════════════════════════

// Agent dostaje feedback i naprawia sam.
// Drugie uruchomienie: ✅ Quality gate ZALICZONY
  • Automatyczny feedback bez Twojej interwencji
  • Agent naprawia sam na podstawie komunikatów hooka
  • Warningi jako błędy – brak kumulowania długu technicznego
  • Zero ręcznego overhead – hook odpala się zawsze
🔧 Co dodałem – hook dotnet-quality-gate (wpis 9)
Trzy pliki: dotnet-quality-gate.json (konfiguracja), dotnet-quality-gate.sh (bash), dotnet-quality-gate.ps1 (PowerShell). Trzy kroki: dotnet format --verify-no-changes, Roslyn Analyzers z FAIL_ON_WARNINGS=true, build weryfikacyjny.
15 min → 0manualny overhead
|
100%sesji z quality gate
|
0warningów na main
8
Automatyczny triage nowych issues 24/7
Scenariusz: Klient zgłasza bug w nocy – co się dzieje zanim ktokolwiek usiądzie do komputera?
Workflow
❌ Przed – bez agentic workflow
// 22:47 – klient tworzy issue:
"Zamówienie się nie przetwarza"
// Brak etykiet, brak assignee, niejasny opis

// 08:15 – developer siada do pracy
→ widzi issue bez kontekstu
→ 5 minut na zrozumienie problemu
→ pyta klienta o szczegóły (odpowiedź: +4h)
→ dopiero o 12:30 można zacząć pracę

// MTTR (mean time to resolution): 14+ godzin
  • Issue bez etykiet czeka na ręczny triage
  • Brak reakcji przez całą noc i poranek
  • Developer traci czas na zbieranie kontekstu
  • Klient nie dostaje potwierdzenia że ktoś patrzy
✅ Po – z agentic workflow issue-triage (wpis 10)
// 22:47 – klient tworzy issue
// 22:47 + ~90 sekund – workflow odpala się:

// Copilot dodaje komentarz:
"Dziękuję za zgłoszenie! 🙏

Proponowane etykiety: bug, orders-module

Żeby szybciej pomóc, czy możesz podać:
- ID zamówienia którego dotyczy problem
- Komunikat błędu który widzisz
- Czy problem dotyczy wszystkich zamówień
  czy tylko konkretnych?"

// 22:48 – etykiety dodane automatycznie
// 23:12 – klient odpowiada z ID zamówienia
// 08:15 – developer ma pełny kontekst
// może od razu zacząć pracę
// MTTR: ~4 godziny (było 14+)
  • Natychmiastowa reakcja – klient wie że issue dotarło
  • Etykiety dodane automatycznie – queue posortowana
  • Brakujące informacje zebrane zanim developer siądzie
  • Developer startuje z pełnym kontekstem
🔧 Co dodałem – agentic workflow issue-triage.md (wpis 10)
Workflow wyzwalany przez issues: [opened]. Agent czyta issue, proponuje etykiety, sprawdza duplikaty, zostawia przyjazny komentarz po polsku z listą brakujących informacji. Uprawnienia: tylko issues: write. safe-outputs: add-issue-comment: true.
14h → 4hMTTR dla bugów
|
<2 minczas pierwszej reakcji
|
24/7dostępność triage
Wszystko razem

Każdy z ośmiu przykładów powyżej to osobna poprawa. Ale prawdziwa wartość pojawia się gdy wszystkie warstwy działają razem. Poniżej jak wygląda typowy dzień pracy w projekcie z pełną konfiguracją:

🌟 Typowy dzień z pełną konfiguracją agentic development
  1. 08:00 – Startujesz. Agentic workflow już przygotował raport backlogu z nocnymi zgłoszeniami, potencjalnymi duplikatami i priorytetami. Codzienne 5 minut "sprawdzania co jest do zrobienia" zajmuje 30 sekund.
  2. 08:30 – Wybierasz zadanie. Delegujuesz implementację nowego endpointu do Coding Agenta przez dobrze opisane issue (wzorzec z wpisu 11). Skill scaffold-api-endpoint daje mu szablony. Idziesz na kawę.
  3. 09:15 – Wracasz. Draft PR gotowy. Hook quality gate sprawdził formatowanie, Roslyn Analyzers i build – wszystko zielone. Otwierasz review przez agenta dotnet-code-reviewer – po 2 minutach masz listę BLOCKER/WARNING/INFO z kodem poprawek.
  4. 09:30 – Jeden komentarz. Zostawiasz @copilot z jedną poprawką architektury. Agent odpowiada, naprawia, aktualizuje PR. Hook odpala się ponownie – zielony.
  5. 09:45 – Merge. Kolega robi final review (wy macie branch protection z wymaganym review) i merge'uje. Endpoin jest w produkcji przed 10:00.
  6. Wieczór. Klient zgłasza bug. Workflow triage odpowiada w 90 sekund, zbiera informacje. Rano developer ma pełny kontekst i może od razu zacząć naprawę.
Czynność Przed Po Narzędzie
Triage porannego backlogu ~5 min ~30 sek Workflow
Implementacja nowego endpointu ~2 godz ~20 min (review) Coding Agent + Skills
Quality gate po sesji agenta ~15 min ręcznie 0 min (automatycznie) Hook
Code review PR ~20-30 min ~5 min (human final) Agent
Pierwsza reakcja na bug klienta nast. rano <2 min 24/7 Workflow
Pisanie testów dla nowej klasy ~45 min ~5 min (review) Skill
Onboarding nowego developera ~2 tygodnie ~3 dni Instrukcje + Plugin
Podsumowanie

Osiem przykładów, osiem konkretnych ulepszeń. Każde z nich wymagało skonfigurowania jednej rzeczy – instrukcji, skilla, agenta, hooka lub workflow. Żadne nie wymagało godzin pracy, żadne nie wymaga utrzymywania skomplikowanego systemu.

  • Instrukcja (wpisy 4–5) eliminuje ręczne poprawki dla powtarzalnych wzorców – HTTP client, error handling, konwencje kodu.
  • Skill (wpis 6) standaryzuje złożone zadania z bogatym kontekstem – testy, scaffolding endpointów, migracje EF Core.
  • Agent (wpis 7) zastępuje godzinne code review zautomatyzowaną analizą z priorytetami i gotowymi poprawkami.
  • Hook (wpis 9) zamienia 15 minut ręcznego quality gate w automatyczny feedback loop dla agenta.
  • Workflow (wpis 10) daje projektowi reakcję 24/7 na zgłoszenia klientów i automatyczny triage bez dyżurów.
  • Kumulatywny efekt: każda warstwa wzmacnia pozostałe. Razem skracają czas od issue do produkcji z godzin do minut.

W kolejnym i przedostatnim wpisie: Słownik terminologii GitHub Copilot – zwięzły słownik wszystkich pojęć które pojawiły się przez cały cykl, gotowy do podesłania osobie zaczynającej przygodę z agentic development.