AI ile Özellik Geliştirme Akışı: Keşfet → Planla → Uygula → Doğrula ve Spec-driven Development
Agent'la özellik geliştirmenin güvenli yolu işi keşfet, planla, uygula ve doğrula adımlarına bölüp planı yazılı ve onaylı tutmaktır; böylece her diff'i bir plana göre okursun. Bookstore'a istek listesini gerçek model çıktılarıyla ekliyor, planı review ediyor, test-first ilerliyor, plandan sapan kodu yakalıyor ve Spec Kit'in akışını, context yönetimini ve git worktree ile paralel agent'ları ele alıyoruz.
Sürüm notu: Bu yazıdaki kodlar .NET 10, Entity Framework Core 10.0.15 ve xUnit.net v3 4.0.3 ile derlenip denenmiştir; model çıktıları yerel Ollama 0.34.4 üzerinde gemma3:12b ile alınmıştır. Spec Kit bilgisi v1.4.0 (10 Kasım 2026) dokümanından alınmış, bu yazıda çalıştırılmamıştır.
Önce Tahmin Et
Bookstore'a istek listesi (wishlist) eklenecek: müşteri bir liste oluşturur, ISBN (kitapların uluslararası standart numarası) ile kitap ekler, çıkarır ve listeyi kitabın güncel adı ve fiyatıyla görür. Agent'a bunu tek cümleyle verdin ve 10 dosyalık, 1.200 satırlık bir diff geldi; içinde bir migration, yeni bir module ve Program.cs değişikliği var.
Review'a nereden başlarsın? Ve daha önemlisi: bu diff'in senin istediğin şeyi yaptığını, ondan fazlasını ya da eksiğini yapmadığını neye göre anlayacaksın? Elinde yazılı bir plan yoksa karşılaştıracak bir şey de yok. Bu yazıda aynı özelliği dört adımda yapacağız ve her adımda neyi onayladığını göreceksin.

Tek Prompt Neden Kötü Bir Başlangıç
"İstek listesi özelliğini ekle" prompt'u agent'a üç kararı birden bırakıyor: ne yapılacağı (kapsam), nasıl yapılacağı (tasarım) ve ne zaman bitmiş sayılacağı (doğrulama). Agent bu kararları sessizce verir ve sen onları ancak diff'in içinden tek tek çıkarabilirsin. Kararlardan biri yanlışsa, örneğin listeye adet eklenmişse ya da yeni bir service katmanı icat edilmişse, düzeltmek için kodun yarısını geri almak gerekir.
İşi adımlara bölmenin amacı agent'ı yavaşlatmak değil, her kararı ucuz olduğu anda görmek. Plan aşamasında bir satırı değiştirmek on saniye sürer; aynı kararı merge'den sonra değiştirmek bir migration ve bir geri dönüş demek. Kent Beck'in ilk yazıda andığımız plan dosyası yaklaşımı da bu: agent'ın neyi, hangi sırayla yapacağı önce yazılır ve onaylanır.
Keşfet: Önce Kodu Okut
İkinci yazıda gördük ki talimat dosyası kuralları veriyor, kodu vermiyor. Keşif adımının işi bu boşluğu kapatmak: agent ilgili dosyaları okur, sen de onun neyi okuduğunu görürsün. Gerçek bir agent dosyaları kendisi bulur; deneyde yerel modele AGENTS.md ile birlikte altı dosyayı (Program.cs, BookstoreContext, sepetin entity'si, configuration'ı ve endpoint'leri, CatalogService) verdim ve kod yazmasını açıkça yasakladım:
Task: customers want a wishlist. They can create a wishlist, add a book to it by ISBN, remove a book, and see the list
with title and current price. There is no login yet; carts are anonymous and identified by a Guid, wishlists can work the same way.
Do NOT write code yet. Read the files above and reply with a plan:
1. Files to add and files to change (exact paths).
2. Endpoints with method, path, request and response, status codes.
3. Data model and the migration step.
4. Tests to write first.
5. Open questions you would ask before implementing.
Keep it under 400 words.
Context 4.109 token tuttu, cevap 71 saniyede geldi. "Kod yazma, önce plan ver" cümlesi araçlarda farklı adlarla geçiyor (plan mode, ask mode, read-only mode); hangisini kullanırsan kullan, amaç aynı: ilk turda dosya değişmesin.
Planla: Planı Review Etmek
Modelin planından birkaç parça, aynen:
* **New File:** `src/Bookstore.Api/Wishlist/WishlistService.cs` - Implements the `IWishlistService` interface.
* **New File:** `src/Bookstore.Api/Data/BookstoreContext.cs` - Add `Wishlist` and `WishlistBook` entities.
* **Change:** `src/Bookstore.Api/Program.cs` - Add `AddWishlistModule()` to service registration.
* **Add Book to Wishlist:**
* Method: `POST`
* Path: `/api/wishlists/{wishlistId:guid}/items/{isbn}`
* `WishlistBook`: `WishlistId (Guid)`, `BookId (int)`, `Quantity (int)`
* What happens if a book's price changes between when it's added to the wishlist and when the user views it?
* What is the maximum quantity of a book that can be added to a wishlist?
Plan büyük ölçüde makul; endpoint'ler, Guid kimlik ve test önceliği doğru. Ama review'da düzeltilmesi gereken yerler var ve hepsi kod yazılmadan önce yakalanıyor:
Quantity: istekte adet yok. Plan kapsamı sessizce genişletmiş, sonra bunu açık soru olarak da sormuş. Karar: adet yok, kitap ya listededir ya değildir.WishlistServiceveIWishlistService: Bookstore'da sepet endpoint'leriBookstoreContext'i doğrudan kullanıyor; tek kullanıcısı olacak yeni bir soyutlama gereksiz.- Ekleme için
POST: sepette aynı işlemPUT; aynı kitabı iki kez eklemek hata olmamalı, yani işlem idempotent. Bunun için (WishlistId, BookId) üzerinde unique index de planda yok. BookstoreContext.cs"New File" olarak yazılmış, oysa var olan bir dosya; endpoint'leri map edenMapWishlistEndpoints()çağrısı da unutulmuş.- Fiyat sorusu iyi bir soru, cevabı istekte var: güncel fiyat gösterilecek, fiyat saklanmayacak.
Bu kararları agent'ın sonraki her adımda okuyacağı bir dosyaya yazdım. Plan dosyası repo'da duruyor; hem agent hem pull request'i review eden kişi aynı metne bakıyor:
# Plan: wishlist
Goal: an anonymous customer keeps a list of books to buy later and sees each book's current title and price.
## Decisions (reviewed)
- Module folder `src/Bookstore.Api/Wishlists/`, SQL schema `wishlists`. No separate service class:
endpoints use `BookstoreContext` and `ICatalogService`, like `OrderingModule`.
- A wishlist is identified by a Guid (`Guid.CreateVersion7()`), like a cart. No quantity: a book is either on the list or not.
- Prices are not stored; `GET` shows the current price from the catalog.
- Adding the same book twice is not an error (idempotent `PUT`). Unique index on (WishlistId, BookId).
## Endpoints
- `POST /api/wishlists` → 201 + `WishlistView`, Location `/api/wishlists/{id}`
- `GET /api/wishlists/{id}` → 200 `WishlistView` | 404
- `PUT /api/wishlists/{id}/items/{isbn}` → 200 `WishlistView` | 404 (wishlist or book not found)
- `DELETE /api/wishlists/{id}/items/{isbn}` → 200 `WishlistView` | 404
`WishlistView(Guid Id, IReadOnlyList<WishlistLineView> Items)`,
`WishlistLineView(string Isbn, string Title, decimal Price, DateTime AddedAtUtc)`, items ordered by title.
## Steps
- [x] 1. Integration tests in `tests/Bookstore.Api.Tests/WishlistTests.cs` (red)
- [x] 2. Entities `Wishlist`, `WishlistItem` + configuration + `DbSet` + migration `AddWishlists`
- [x] 3. `WishlistsModule` with the four endpoints; register in `Program.cs` (green)
- [x] 4. Review the diff, run all tests, commit
Uygula: Test-first ve Küçük Adımlar
Planın ilk adımı testler. Modelden yalnız birinci adımı yapmasını, testlerde henüz var olmayan WishlistView'ü kullanmasını istedim. Altı test yazdı; ikisinden kısa bir parça:
Assert.Equal(HttpStatusCode.Ok, response.StatusCode);
WishlistView? wishlist = await response.Content.ReadFromJsonAsync<WishlistView>(ct);
Assert.NotNull(wishlist);
Assert.Equal(1, wishlist.Items.Count);
> dotnet build
WishlistTests.cs(45,37): error CS0117: 'HttpStatusCode' does not contain a definition for 'Ok'
WishlistTests.cs(46,75): error CS0246: The type or namespace name 'WishlistView' could not be found (are you missing a using directive or an assembly reference?)
Build FAILED.
Testlerin derlenmemesi bu adımda beklenen kırmızı; WishlistView henüz yok. Ama hataların biri beklenmedik: HttpStatusCode.Ok diye bir değer yok, doğrusu OK. Model bir de using Bookstore.Wishlists; satırını unutmuş, kullanılmayan beş using eklemiş. Daha önemlisi, testlerde planın iki kararı hiç yok: aynı kitabı iki kez eklemek ve bilinmeyen bir ISBN. Güncel ad ve fiyatı doğrulayan bir test de yok; oysa özelliğin asıl değeri bu. Testleri plan maddelerine göre yeniden yazdım; kalan beş testten biri şu:
[Fact]
public async Task Adding_the_same_book_twice_keeps_one_line()
{
Guid id = await CreateWishlistAsync();
await client.PutAsync($"/api/wishlists/{id}/items/{TheHobbit}", null, ct);
HttpResponseMessage second = await client.PutAsync($"/api/wishlists/{id}/items/{TheHobbit}", null, ct);
Assert.Equal(HttpStatusCode.OK, second.StatusCode);
WishlistView? wishlist = await second.Content.ReadFromJsonAsync<WishlistView>(ct);
Assert.Single(wishlist!.Items);
}
Ders şu: testi agent'a yazdırmak, testi review etmeme izni vermiyor. Test, agent'ın sonraki adımda yazacağı kodun hakemi; hakemi de agent seçerse kendi kodunu kendisi onaylamış olur. Test-first'ün agent'la nasıl yürüdüğü TDD (Test-Driven Development; önce testi yazma yaklaşımı) serisinin 7. yazısında ayrıca anlatılıyor.
İkinci ve üçüncü adımı (entity, configuration, migration ve endpoint'ler) modele tek seferde verdim. 200 saniyede 6 dosya ve 187 satır geldi. Önce diff'i plana göre okudum:
// src/Bookstore.Api/Data/Wishlist.cs
namespace Bookstore.Data;
public sealed class Wishlist
{
public Guid Id { get; set; }
public DateTime CreatedAtUtc { get; set; }
public DateTime UpdatedAtUtc { get; set; }
public IReadOnlyCollection<WishlistItem> Items { get; } = [];
}
// src/Bookstore.Api/Wishlists/WishlistsModule.cs
public static IServiceCollection AddWishlistsModule(this IServiceCollection services) =>
services.AddScoped<WishlistService>();
Entity'ler planda yazan Wishlists/ klasörü yerine Data/ altına gitmiş, plan "service class yok" dediği hâlde var olmayan bir WishlistService kaydedilmiş. WishlistItem, kitaba int BookId yerine ISBN string'iyle bağlanmış; bu hem sepetin modelinden farklı hem de ICatalogService.GetByIdsAsync'in imzasına uymuyor. Build de aynı şeyi söyledi:
> dotnet build
Data/Wishlist.cs(18,19): error CS8618: Non-nullable property 'Isbn' must contain a non-null value when exiting constructor.
Data/WishlistConfiguration.cs(24,21): error CS0246: The type or namespace name 'Book' could not be found
Wishlists/WishlistsModule.cs(12,28): error CS0246: The type or namespace name 'WishlistService' could not be found
Wishlists/WishlistsModule.cs(46,49): error CS1061: 'DbSet<Wishlist>' does not contain a definition for 'AsNoTracking'
Wishlists/WishlistsModule.cs(73,13): error CS1929: 'IReadOnlyCollection<WishlistItem>' does not contain a definition for 'Add'
Wishlists/WishlistsModule.cs(113,83): error CS1503: Argument 1: cannot convert from 'string[]' to 'System.Collections.Generic.IReadOnlyCollection<int>'
# ... toplam 13 farklı hata
Küçük bir yerel modelden bu beklenen bir sonuç; bulut modelleri bu boyutta bir adımı çoğunlukla derlenir hâlde getirir. Asıl ders derleme hatalarında değil, plandan sapmalarda: kod derlenseydi bile entity'nin yeri, service class'ı ve ISBN ile kurulan ilişki plana aykırıydı ve bunları ancak elinde yazılı bir plan varsa sapma olarak görebilirsin. Bu yüzden ikinci ve üçüncü adımı birleştirmek hataydı; her adım ayrı olsaydı entity'deki sapmayı endpoint'ler yazılmadan önce görecektim.
Modelin kodunu atıp adımları plana göre ben yazdım. Ekleme endpoint'i şöyle:
// PUT /api/wishlists/{id}/items/{isbn} → kitabı ekler; tekrar eklemek bir şey değiştirmez
private static async Task<Results<Ok<WishlistView>, NotFound<string>>> AddItem(
Guid wishlistId, string isbn, BookstoreContext db, ICatalogService catalog, TimeProvider clock, CancellationToken ct)
{
Wishlist? wishlist = await db.Wishlists.Include(w => w.Items).FirstOrDefaultAsync(w => w.Id == wishlistId, ct);
if (wishlist is null)
{
return TypedResults.NotFound("Wishlist not found.");
}
BookInfo? book = await catalog.FindByIsbnAsync(isbn, ct);
if (book is null)
{
return TypedResults.NotFound($"Book {isbn} not found.");
}
if (wishlist.Items.All(i => i.BookId != book.Id))
{
DateTime now = clock.GetUtcNow().UtcDateTime;
wishlist.Items.Add(new WishlistItem { BookId = book.Id, AddedAtUtc = now });
wishlist.UpdatedAtUtc = now;
await db.SaveChangesAsync(ct);
}
return TypedResults.Ok(await ToViewAsync(wishlist, catalog, ct));
}
Migration'ı AGENTS.md'deki komutla ürettim; elle tek satırına dokunmadım:
> dotnet ef migrations add AddWishlists --project src/Bookstore.Api
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
Doğrula: Build, Test ve Diff
Doğrulama üç parçalı: build, testlerin tamamı ve diff'in plana göre okunması. Yalnız yeni testleri değil, hepsini çalıştırıyoruz; sepet ve sipariş testleri yeni tablonun bir şeyi bozmadığını gösteriyor:
> dotnet test
Test run summary: Passed!
total: 15
failed: 0
succeeded: 15
skipped: 0
duration: 3s 231ms
> git diff --cached --stat
docs/plans/wishlist.md | 8 +-
src/Bookstore.Api/Data/BookstoreContext.cs | 4 +-
.../20261114092201_AddWishlists.Designer.cs | 927 +++++++++++++++++++++
.../Data/Migrations/20261114092201_AddWishlists.cs | 87 ++
.../Migrations/BookstoreContextModelSnapshot.cs | 64 ++
src/Bookstore.Api/Program.cs | 2 +
src/Bookstore.Api/Wishlists/Wishlist.cs | 17 +
.../Wishlists/WishlistConfiguration.cs | 24 +
src/Bookstore.Api/Wishlists/WishlistContracts.cs | 5 +
src/Bookstore.Api/Wishlists/WishlistsModule.cs | 93 +++
10 files changed, 1226 insertions(+), 5 deletions(-)
Girişteki 1.200 satırın büyük kısmı migration'ın üretilmiş designer dosyası; elle yazılan kod 150 satırın altında. Diff'te plan dışı bir dosya yok: Program.cs'de yalnız iki satır, BookstoreContext'te bir DbSet ve bir using. Plan dosyasındaki adımları işaretleyip commit attım. Her yeşil adımdan sonra commit, agent'la çalışırken checkpoint işlevi görür: sonraki adım yanlış giderse git restore ya da git reset ile bir adım geriye dönmek, agent'tan "son değişikliği geri al" diye istemekten daha güvenilir.
Spec-driven Development ve Spec Kit
Bu yazıdaki plan dosyası, spec-driven development'ın (önce yazılı bir spec, sonra ondan türeyen plan, görev listesi ve kod) hafif bir hâli. GitHub'ın açık kaynak Spec Kit aracı bu akışı komutlara bölüyor. 2026-11 itibarıyla son sürüm v1.4.0 (10 Kasım 2026) ve dokümanındaki komutlar şöyle; bu yazıda Spec Kit'i çalıştırmadım, aşağıdaki özet dokümandan:
| Komut | Ne yapar | Ürettiği |
|---|---|---|
/speckit.constitution | Projenin değişmeyen ilkelerini yazar | Constitution dosyası |
/speckit.specify | Özelliği "ne ve neden" olarak tarif eder | spec.md |
/speckit.clarify | En fazla beş soru sorup cevapları spec'e yazar (isteğe bağlı) | spec.md |
/speckit.plan | Spec'ten teknik tasarım çıkarır | plan.md ve tasarım dosyaları |
/speckit.tasks | Bağımlılık sırasına göre görev listesi yazar | tasks.md |
/speckit.analyze | Spec, plan ve görevler arasındaki tutarsızlıkları raporlar (salt okunur) | Rapor |
/speckit.implement | Görevleri uygular | Kod |
/speckit.converge | Kodu spec, plan ve görevlerle karşılaştırır, eksikleri yeni görev olarak ekler | tasks.md |
Komutlar terminalde değil, kullandığın coding agent'ın içinde çalışıyor; specify init komutu seçtiğin agent için gerekli dosyaları kuruyor ve doküman 40'tan fazla agent entegrasyonu sayıyor. Akışın değeri, her adımın bir öncekinin çıktısını girdi olarak alması ve aradaki her dosyanın review edilebilmesi. Bedeli de tören: birkaç satırlık bir değişiklik için constitution, spec, plan ve tasks dosyası yazmak, işin kendisinden uzun sürer. Benim pratik ayrımım şu: birden çok dosyaya, veri modeline ya da birden çok kişiye dokunan özelliklerde spec ve plan yazmaya değer; tek dosyalık bir düzeltmede plan, prompt'un içinde iki cümle olarak kalabilir.
Uzun Oturum, Context ve Paralel Agent'lar
Agent'la uzun bir oturumda context dolar: okunan dosyalar, komut çıktıları, denenip vazgeçilen yollar. Araçlar context'in eski kısmını kısa bir özete çevirerek (compaction) yer açar, ama özet her zaman kayıplıdır ve agent'ın ilk kararları unutması tipik bir belirtidir. Plan dosyasının ikinci faydası burada: yeni bir oturum açıp "docs/plans/wishlist.md'yi oku, işaretlenmemiş ilk adımdan devam et" demek, kirlenmiş bir oturumu sürdürmekten genellikle daha iyi sonuç verir. Context'in nasıl yönetildiği AI Agent'ları ve MCP (Model Context Protocol; agent'ları dış araç ve verilere bağlayan protokol) serisinin 4. yazısında anlatılıyor.
İki agent'ı aynı anda çalıştırmak istiyorsan, örneğin biri API'yi, diğeri Angular sayfasını yazarken, ikisini aynı klasörde çalıştırma; aynı dosyalara yazar ve birbirinin build'ini bozarlar. Her agent'a kendi git worktree'sini ver (Git & GitHub serisinin 24. yazısı):
> git worktree add ../bookstore-wishlist-ui -b feature/wishlist-ui
Preparing worktree (new branch 'feature/wishlist-ui')
HEAD is now at 5b4bb5f 03: wishlist module, migration AddWishlists
> git worktree list
C:/src/bookstore 5b4bb5f [main]
C:/src/bookstore-wishlist-ui 5b4bb5f [feature/wishlist-ui]
Paralel agent sayısını review kapasiten belirler, agent'ın hızı değil. İki agent'ın açtığı iki pull request'i dikkatle okuyamıyorsan, ikinci agent zaman kazandırmıyor, review kuyruğu biriktiriyor.
Üretimde Dikkat
Plan dosyasını pull request'e dahil et ve review'da diff'le birlikte oku; reviewer'ın ilk sorusu "bu diff plandaki adımları mı yapıyor?" olmalı. Plan dışı değişiklik varsa ya plan güncellenir ya değişiklik geri alınır. Migration'lar her zaman araçla üretilmeli ve diff'te ayrıca kontrol edilmeli: tablo adı, schema, index ve onDelete davranışı production verisini etkiler.
Agent'ın çalıştırdığı komutları gözden kaçırma. Migration'ı veritabanına uygulayan bir dotnet ef database update ya da paket ekleyen bir dotnet add package, onay istemeden çalışmamalı. Bunların nasıl kısıtlanacağı beşinci yazının konusu.
Sık Düşülen Tuzaklar
- Tek dev prompt ile "hepsini yap" demek: kapsam, tasarım ve doğrulama kararlarının hepsi agent'a kalır.
- Planı okumadan onaylamak:
Quantitygibi istenmemiş bir alan plandan koda, oradan migration'a geçer. - Testleri agent'a yazdırıp review etmemek: eksik vaka, agent'ın kodunu sınamayan bir hakem demektir.
- Birden çok plan adımını tek seferde uygulatmak: sapmayı geç fark edersin.
- Plan dışı değişiklikleri fark etmemek: diff'i plana göre değil, satır satır okumak bunları gözden kaçırır.
- Kirlenmiş uzun bir oturumda devam etmek yerine plan dosyasıyla yeni oturum açmamak.
- İki agent'ı aynı klasörde çalıştırmak.
Küçük Bir Deneme
★☆☆ GET /api/wishlists/{id} için ayrı bir test yaz: iki kitap ekle, listeyi GET ile oku ve sıralamanın kitap adına göre olduğunu doğrula. Bu test hangi plan maddesini koruyor?
★★☆ Bookstore'un Angular istemcisine istek listesi sayfası eklemek için önce yalnız bir plan iste: dosyalar, store, route ve testler. Planı web/bookstore-web/AGENTS.md'deki kurallarla karşılaştır ve uymayan maddeleri düzeltip docs/plans/ altına kaydet.
★★★ "İstek listesindeki bir kitabı sepete taşı" özelliğini dört adımda yap. Plan aşamasında şu sorulara cevap ver: sepet yoksa ne olur, kitap stokta yoksa ne olur, kitap listeden silinir mi? Her adımdan sonra commit at ve sonunda git log'un plan adımlarını okunur şekilde gösterip göstermediğine bak.
Bu yazıda agent'ın kodunu plana göre okuyup derlenmediği ve plandan saptığı için attık. Her zaman bu kadar kolay olmayacak: derlenen, testleri geçen ama yine de yanlış olan kod daha tehlikeli. Dördüncü yazıda AI'ın yazdığı kodun tipik hatalarını, bir review checklist'ini ve AI reviewer'ların neyi yakalayıp neyi kaçırdığını göreceğiz.