sozsoft-platform/claude.md
2026-10-05 17:11:06 +03:00

44 KiB
Raw Blame History

Sozsoft Platform - Claude Instructions

This file provides Claude-specific operating rules for this repository. Primary source of truth for platform behavior is:

  • .github/instructions/ai.instructions.md

If there is any conflict, follow .github/instructions/ai.instructions.md.

Communication Rules (apply to every response)

  1. Never introduce or summarize the platform. Assume the user knows what this application is, how it is built, and which technologies it uses. Do not open a response with "Sozsoft Platform is a multi-tenant low-code engine…" or any equivalent framing.
  2. Answer the request, nothing more. No unsolicited architecture overviews, no restating the decision order, no re-explaining ListForm/DeveloperKit concepts unless the user actually asked about them.
  3. No preamble, no epilogue. Skip "Great question", "I reviewed the codebase", summaries of what you are about to do, and closing recaps of what you just did.
  4. Match the question's size. A yes/no question gets a sentence. A config question gets the config. Only a full feature request gets a full proposal.
  5. Respond in the language the user writes in (Turkish → Turkish).
  6. Mention a rule from this file only when it changes the answer — never as boilerplate.

Purpose

  • Maximize delivery through runtime configuration.
  • Minimize custom code.
  • Preserve platform consistency, security, and tenant isolation.

Primary principle: Configuration first, code last.

Mandatory Decision Order

For every request, evaluate in this order — internally. State the chosen step only when the user is asking how to build something, and state it in one line, not as a walkthrough of the options you rejected.

  1. Dynamic configuration with existing ListForm ecosystem
  2. SQL Query Manager + Custom Endpoint
  3. Dynamic Service
  4. Code change (last resort, justification required)

Non-Negotiable Rules

  1. Do not propose new custom React component/page development for standard feature requests.
  2. Build new screens using platform configuration mechanisms.
  3. Every implementation proposal must include tenant and permission design.
  4. Never bypass platform authorization patterns.
  5. Never hardcode secrets, tenant IDs, or connection strings.
  6. Never step outside the framework without explicit approval (standing default 11).

Exception:

  • Custom React/backend code is allowed only when the user explicitly requests implementation and configuration is insufficient.
  • In such cases, explain why configuration-first options are not enough — in a sentence or two.

Architecture Guardrails

  • Backend: Respect ABP module boundaries and explicit, auditable permissions.
  • Frontend: Use existing dynamic view infrastructure and metadata-driven behavior.
  • Data: Use parameterized SQL patterns and enforce tenant-safe access.

Dynamic Platform Expectations

  • Menus and routes are database-driven.
  • Dynamic List/Form/Component infrastructure is the default solution path.
  • Keep route, menu, permission, and datasource mappings coherent.

Security and Compliance

  • Enforce RBAC and permission-driven visibility in all layers.
  • Never output real credentials, tokens, keys, or secrets.
  • Use placeholders in examples.
  • Maintain tenant isolation in every query and action.

Response Contract

The list below applies only to a full implementation proposal for a new screen, module, or integration. It is not a template for questions, debugging, code review, refactors, explanations, or small changes.

Even for a full proposal: include only the items that carry real content for that request, and drop the rest. An empty or obvious heading is noise.

  1. Goal
  2. Decision flow result (which step used)
  3. Artifacts to configure
  4. SQL/query/endpoint design (if needed)
  5. Menu + route + component mapping
  6. Permission and role mapping
  7. Tenant isolation notes
  8. Validation and test checklist
  9. Rollback strategy

Standing Context (read this before proposing anything)

These sections encode expectations the user will not repeat in every prompt. README.md is the technical map of the platform; this file is how to act on it.

Where the truth lives

Konu Kaynak
Platform davranışı, karar sırası, çıktı sözleşmesi .github/instructions/ai.instructions.md
Artefakt üretimi: seed şemaları, enum değerleri, çalışan örnekler .github/instructions/lowcode.instructions.md
Yetenek envanteri: JSON kolonlarının alan alan sözleşmesi, grafik/pivot/tasarımcı/mail modülü, Saas + Administration + İK menüsündeki hazır ekranlar .github/instructions/lowcode-reference.instructions.md
api/ kod standardı (sürüm, nullable, ölü kod, modern C#, ABP katmanları) .github/instructions/dotnet.instructions.md
Modül/liste ekleme prosedürü, seeder dosya sorumlulukları .github/instructions/list.instructions.md
Ne nerede yaşıyor, hangi ekran ne yapıyor README.md
Bir modülün nasıl çalıştığı, analizi ve kararları configs/seeds/docs/modules/{Modül}.md (dizin: README.md §10.5)
Yetki kodları PlatformConsts.AppCodes (backend) + constants/permission.constant.ts (UI)
Seed yolları SeedPathResolver

Bir çelişki varsa sıra: ai.instructions.md → dotnet.instructions.md → bu dosya → README.md.

Standing defaults — do not ask again

  1. Yeni tablo → tenant + tam audit kolonları varsayılan gelir (Id, TenantId, CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeletionTime, DeleterId). Kullanıcı açıkça "tenant yok / audit yok" demedikçe çıkarma. Yeni tanım tablosu (tanım olup olmadığına talebin içeriğine göre analizde karar verilir; sabit ad listesi yoktur) buna ek olarak IsActive bit NOT NULL DEFAULT 1 ile gelir; liste + form alanı, varsayılan true, Aktif/Pasif widget'ı. Tanım olmasa da MCP ile (ya da wizard ile) üretilen her ekranda IsActive kolonu aynı varsayılanla gelir: tabloda NOT NULL DEFAULT 1 (create_table'da isNullable: false, defaultValue: "1"), formda yeni kayıt true ile açılır (FormFieldsDefaultValueJson). Ayrıntı: lowcode.instructions.md §8.1. Görsel tutan her kolon (fotoğraf, logo, avatar kopyası) nvarchar(MAX) + editör dxImageUpload; değer base64 saklanır, sınırlı boy ve dxImageViewer kullanılmaz (deploy ikisini de reddeder).
  2. Yeni ekran → Wizard yolu. Definitions altına otomatik yerleştirme yok; modüle ait yeni bir kök menü oluştur, Order = max(Order) + 1. Yetki ağacı menü ağacını izler: ekranın okuma yetkisi menüdeki dalın ara yetkisinin altına yazılır; menüsüz alt formun yetkisi ise listenin yetkisine bağlanır (ParentName = listenin yetki kodu) (lowcode.instructions.md §0.12 Menü yeri).
  3. Yeni ekran/menü → yetki sözleşmesi olmadan önerilmez. Menü ve route aynı ekran sözleşmesini göstermeli.
  4. Kullanıcıya görünen her metin → dil anahtarı üzerinden, EN + TR birlikte. Koda gömülü metin önerme.
  5. Runtime'da üretilen her artefakt (wizard, custom component, crud endpoint) → ilgili seed dosyası da yazılır/güncellenir. Veritabanı sıfırlandığında geri gelmeyen bir şey üretme.
  6. Yeni ekran/komponent talebi — yazılı ya da görsel → dosya yazmadan önce keşif turu: toplu sorularla (tur başına en fazla 4, seçenekli, önerilen varsayılanlı) ekran türü, veri kaynağı, modül, kapsam ve yolun soru setini al; tarifi kur ve onaylat, sonra configs/seeds/ altına üret. Şüphede Wizard. Kullanıcı "sorma / sen seç" derse varsayılanlarla üret ve sonunda listele. Depodan okunabilen şeyi sorma; tabloya yazma hedefi, onaycı ve hesap formülü asla varsayılmaz. Ekran görüntüsü piksel sözleşmesi değildir; görsel Tur 2'nin çoğunu cevaplar, yalnızca görselden okunamayanlar sorulur. Bu kural yalnızca yeni üretim içindir; düzeltme/alan ekleme/debug doğrudan yapılır. Ayrıntı: lowcode.instructions.md §0.6–§0.7.
  7. Ziyaretçiye açık yeni yol (public RoutePath taşıyan custom component, Pub menüsü, yeni public sayfa) → SEO alanları da doldurulur. Ayrı bir SEO tablosu yoktur: meta, yolu tanımlayan kaydın Seo* kolonlarındadır — data/App.Menus.Routes.json ya da data/App.DeveloperKit.CustomComponents/{Id}.json. Metin ::Anahtar ile dil anahtarı taşır. Kullanıcı SEO bilgisi girmez; üreten yazar. Genel varsayılanlar kayıtta değil App.SiteManagement.Seo.* ayarlarındadır. Ayrıntı: lowcode.instructions.md §0.9.
  8. Ekran içi hesap/koşul ihtiyacı → önce EditorScript (Script Builder tarifleri), sonra EditorOptions; kod yazmak son çare.
  9. SQL → her zaman parametreli. String birleştirme ile sorgu kurma.
  10. Silme → DeleteCommand = DefaultDeleteCommand("{Tablo}"); ham SQL string'i yazma.
  11. Çerçeve sınırı → uygulama kendini kendi mekanizmalarıyla geliştirir (seed artefaktları; var olan ui bileşenine eksik olay/prop/küçük yetenek ekleme — komponentin %70'i hazır araçlarla kuruluyorsa kalanı bu eklemelerle tamamlanır, yeni bileşen yazılmaz; geriye dönük uyumlu tarif/seçenek/token ekleme). Bunun dışına çıkan her şey — yeni React bileşeni/sayfası, çekirdek motor, yeni entity/ migration, platform tablosu şeması, yeni paket, Routes, dış entegrasyon — önce sorulur, yalnızca onayla yapılır; onay talebe özeldir. Çerçeve içinde yapılabilen kısım sorudan önce bitirilir. "Yapılamaz" cevabı yoktur. Listeler: lowcode.instructions.md §0.8.
  12. Liste ekranı görünümleri → prompt/görsel/tarifle üretilen her liste ekranı Grid + Card + Pivot + Chart açık, DefaultLayout: "grid" ile gelir; "liste/grid" demek diğerlerini kapatmak değildir. Bir görünüm yalnızca istek onu açıkça dışarıda bıraktığında false yazılır. Tree/Gantt/Scheduler/Todo yalnızca istenince açılır. Ayrıntı: lowcode.instructions.md §9.8.
  13. Liste ekranı widget'ları → Wizard ya da MCP deploy_wizard ile üretilen her List ekranı sorulmadan en az 3 kartlık Widgets ile gelir; kartlar tablonun durum / boolean bayrak / tarih / tutar kolonlarından seçilir, başlıklar dil anahtarıdır (App.Widget.* önce). Kiracı tablosunda her kart "TenantId" = '@TENANTID' taşır; IsDeleted koşulu yazılmaz. Form/Chart, alt form ve basit tanım listeleri istisnadır. Alt form oluşturulurken widget oluşturulmaz: başka bir ekranın SubForms sekmesi olarak üretilen her ekran — menüsüz ya da kendi menüsü olan — Widgets: [] ile deploy edilir. Deploy menüsüz (CreateMenu: false) wizard'ın Widgets'ını ayrıca düşürür; ekran alt form sekmesinde açıldığında şerit de çizilmez (useListFormWidgets(…, isSubForm)) — kart sorgusu ana kayda göre süzülmez. Ayrıntı: lowcode.instructions.md §4.6.1. Alt form ekranının menü/yetki/düzenleme kuralları (menüsüz alt formun yetkisi ana ekranın altında, Form sekmesi tek kayıt + AllowAdding/AllowUpdating açık): lowcode.instructions.md §4.6.
  14. Modül dokümanı → MCP ya da wizard ile oluşturulan her modül çalışma şeklini configs/seeds/docs/modules/{Modül}.md dosyasında taşır: keşif cevapları ve onaylanan tarif (analiz), ekranlar, veri modeli, hesaplar/durum geçişleri, yetki dağılımı, entegrasyonlar, sık sorulanlar. Dosyayı ve "Ek — Ekran envanteri"ni deploy kendisi açar/yeniler; anlatım bölümlerini (module-doc:todo) üreten doldurur — MCP'de save_module_doc, Claude Code'da dosyaya doğrudan. MCP talebi doküman tamamlanmadan Testing'e alınamaz. Wizard dışı modül dokümanı elle yazılır. README'ye yalnızca §10.5 dizin satırı ve ilgili bölümde tek cümlelik bağlantı yazılır. "X nasıl çalışıyor?" sorusunda önce bu dosya okunur. Ayrıntı: lowcode.instructions.md §0.13.
  15. Ekran açıklaması → wizard/MCP ile üretilen her liste ekranının (alt formlar dahil) DescTr/DescEn metni ayrıntılıdır; başlıktaki ⓘ simgesinde gösterilir: ne işe yaradığı, menüde nerede / hangi ekranın sekmesi olduğu, ana alanlar, otomatik hesap ve geçişler, ilişkili ekranlar (RichText biçimi). MCP deploy_wizard 3 satır / 150 karakterin altını reddeder. Ayrıntı: lowcode.instructions.md §4.4.2.

Cross-cutting: touching one thing means touching these too

  • Modül dokümanı → ModuleDocManager.RefreshAllAsync wizard'ı yazan her yoldan çağrılır (deploy, silme, WizardSeedSynchronizer, import kapanışı); wizard dosyasını yazan yeni bir yol eklenirse çağrı da eklenir, yoksa envanter bayatlar. Wizard → doküman eşlemesi tek yerdedir (ModuleDocCatalog: beyan → alt formun ana ekranı → menü dalı); envanter biçimi ModuleDocRenderer'dadır. Export kapanışı dokümanı taşır (WizardExportCollector), import docs/modules/*.md yolunu kabul eder.

  • scriptRecipes.ts (TS) ↔ Domain.Shared/Editors/*.cs — biri değişirse diğeri de değişir. İkisinin çıktısı birebir aynı olmak zorundadır; aksi hâlde seeder ile basılan script dialogda "elle düzenlenmiş" sayılır ve kural editörü kapanır.

  • htmlEditorOptions.ts ↔ EditorOptions.Html — HtmlEditor varsayılanı iki yerde yazılır ve bloklar birebir aynıdır. İstemci iç içe blokları tek seviye birleştirdiği için seed edilen toolbar varsayılanın yerine geçer; biri değişip diğeri değişmezse seed'li ekranlar eski araç çubuğunu gösterir.

  • Wizard seed dosyasında tekrar yok → Wizard bölümü yalnızca ekrandan üretilemeyen bilgileri taşır. ListForm/Fields içinde karşılığı olan her alan (veri kaynağı, select, anahtar alan, kırılım/düzen/düzenleme bayrakları, görünüm option blokları, alt form, widget, iş akışı, alan tanımları) dosyaya yazılmadan çıkarılır (WizardSeedFileDto.ToSeedJson) ve okurken geri doldurulur (FromSeedJson). Wizard'a yeni bir alan eklerken karşılığı ListForm'da varsa listeye ekle (CommandColumns → CommandColumnJson bu yolla taşınır; wizard ekranında adımı yoktur, UI modeli düzenlemede korur). Dosyada kalan wizard alanları üç başlık altında toplanır — Menu, Language, Groups — ve ListFormCode → WizardCode, MenuParentModuleId → ModuleId, LanguageTexts → Language.Texts adıyla yazılır; varsayılanı olan alanlar (CreateMenu: true, boş DataSourceConnectionString, boş MenuParentPath / LanguageTexts) hiç yazılmaz. Bu yalnızca dosya biçimidir; C# sözleşmesi düzdür, dönüşüm WizardSeedFileDto.ToSeedJson / FromSeedJson içindedir. Wizard seed dosyası her zaman bu iki metotla okunup yazılır; düz JsonSerializer çağrısı bloklu bölümleri sessizce düşürür.

  • Wizard ile üretilen ekran → seed dosyası (configs/seeds/wizard/{WizardName}.json) wizard cevaplarının yanında ekranın GridOptionsEditDto (ListForm) ve alanların ColumnFormatEditDto (Fields) sözleşmesini taşır. ListForm detayından yapılan her kayıt (ListFormsAppService, ListFormFieldsAppService, ListFormJsonRowAppService) bu bölümleri ve Wizard bölümündeki karşılıklarını geri yazar (WizardSeedSynchronizer). Bu üçlüden birine yeni bir kayıt yolu eklenirse senkronizasyon çağrısı da eklenir; yoksa dosya bayatlar.

  • Wizard girdisinden ekran üretimi → tek kaynak ListFormWizardDto.BuildListForm/BuildFields, entity'ye yazma GridOptionsEditDto.ApplyTo / ColumnFormatEditDto.ApplyTo. Deploy, seeder ve senkronizasyon aynı metotları çağırır; ikinci bir üretim kopyası yazma.

  • Katman sınırı (DDD) → DbMigrator Application'a referans vermez. Seeder ile AppService'in paylaştığı dönüşüm Application.Contracts'ta DTO metodudur; entity'ye yazma Domain.Shared'deki IListFormArtifact / IListFormFieldArtifact sözleşmeleri üzerinden yapılır. Ayrıntı: dotnet.instructions.md §5.

  • Yeni bir yetki → PlatformConsts.AppCodes + data/Abp.Identity.Permissions.json + (UI'da kullanılacaksa) permission.constant.ts + data/App.Languages.LanguageText.json. Yetki ekranındaki sıra AbpPermissions.Order (grup için AbpPermissionGroups.Order) sütunundan gelir ve seed satırındaki Order alanıyla yazılır; sıra kardeşler arasındadır — her üst yetkinin altında 1'den başlar, yeni satırı menüdeki yerine denk gelen numarayla ekle.

  • /mcp ucuna yeni bir araç → üç yer birlikte değişir: McpToolNames (ad kayıt defteri), ilgili Mcp*Tools sınıfı (Sozsoft.Mcp.HttpApi) ve arkasındaki AppService sözleşmesi (Sozsoft.Mcp.Application.Contracts). Arac adı McpToolNames.All içinde yoksa araç listesi süzgecinden geçmez ve hiç görünmez; MCP Servers ekranındaki AllowedTools seçenekleri de SelectableTools'dan üretildiği için kendiliğinden güncellenir. Sunucu kaydında tek anahtar AllowedTools'tur: boşsa tüm araçlar, doluysa yalnızca seçilenler açıktır; araç tipine göre ayrı bir bayrak yoktur, sınırı kullanıcı yetkisi çizer. İki MCP yetkisi vardır: .Invoke uca erişim ve okumadır (MCP servislerinin sınıf düzeyinde aranır); .Write MCP üzerinden yapılan her değişikliktir ve değişiklik yapan her araç RequiredPermissions'ta .Write'a bağlanır — okuma/yazma ayrımı için yeni yetki açılmaz. Kiracı veri okur ve yazar, geliştirme yapamaz: kiracı bağlamında açık araçlar McpToolNames.TenantTools ile sınırlıdır (şema, query_list, run_select, insert_row/update_row/delete_row) ve AllowedTools bu kümeyi yalnızca daraltır. Satır yazma ekranın C/U/D sözleşmesinden geçtiği için kiracıya açıktır; ekran yetkisinden geçmeden yazabilen araç (procedure, Custom Endpoint) ve geliştirme araçları (DevelopTools) TenantTools'a eklenmez. Geliştirme işlemi yine Wizard / Custom Components AppService'inden geçer. /mcp istek düzeyindeki unit of work'ün dışındadır (AbpAspNetCoreUnitOfWorkOptions.IgnoredUrls): cevap SSE ile aktığı için istek UoW'si sonuç istemciye gittikten sonra commit edilir ve bağlantı kapanınca geri alınır — araç başarılı görünür, EF kayıtları kaybolur. Her MCP servis çağrısı kendi UoW'sini sonuç dönmeden tamamlar; bu istisna kaldırılmaz. Kapı iki yerdedir: AddListToolsFilter listeyi süzer, McpAppService.EnsureToolAllowedAsync çağrıyı reddeder — süzme tek başına yetki sınırı değildir, ikisi birlikte yazılır. Invoke uçta değil her MCP servisinin sınıf düzeyindeki [Authorize]'dadır; yeni bir MCP AppService'i de bu attribute'la açılır. Hata istemciye yalnızca McpException olarak ulaşır; McpToolErrorTranslator (çağrı filtresi) yetki reddini "şu yetkiniz yok" mesajına, UserFriendlyException'ı mesajına çevirir. Kullanıcının göreceği her hata bu iki türden biriyle atılır. Yeni aracın arkasındaki servis bir yetkiyi [Authorize] attribute'uyla istiyorsa (ret mesajı yetki adını taşımaz) o yetki McpToolNames.ServicePermissions'a eklenir; ekran yetkisi gereken araç yetkiyi IMcpListFormAccessFilter.GetPermissionNameAsync + CheckAsync ile adıyla önceden ister. Araç → yetki tablosu README.md §13.9'dadır; araç ya da yetki değişirse tablo da değişir. Hazır prompt'lar (McpPromptNames + McpDevelopPrompts) yalnızca metindir; adımları tekrar etmez, get_dev_guide bölümüne yönlendirir. Bir kural bağlayıcıysa prompt'a değil araca yazılır (örnek: Ready to Start şartı start_dev_request'tedir).

  • Wizard deploy veritabanına yazmaz → wizard ekranı, MCP deploy_wizard ve düzenleme aynı yoldan (ListFormWizardAppService.Create) geçer ve yalnızca seed dosyası yazar: wizard/{Ad}.json

    • menü/modül/izin grubu/yetki/dil satırları (data/App.Menus.{Menu,Module}.json, data/Abp.Identity.Permission{Groups,s}.json, data/App.Languages.LanguageText.json; WizardSeedRowStore, anahtarlar WizardSeedRowKeys). Kurulum yalnızca DB Migrate'tedir: WizardDataSeeder ekrana özgü kayıtları (veri kaynağı, ListForm, alan, iş akışı) kurar, ListFormSeedDataApplier satırları ekler. Deploy/import dosyaya Pending yazar; Wizard Manager onu "Migrate bekliyor" gösterir (WizardFileInfoDto.IsPending), migrate önceki sürümün kayıtlarını (Pending.Replaced) kaldırıp ekranı yeniden kurar ve Pending'i siler. Yetkiler hiçbir role otomatik atanmaz (admin dahil); rol ekranından verilir. Deploy'a veritabanı yazan ya da silen bir adım ekleme. Ayrıntı: lowcode.instructions.md §4.0.
  • MCP ile demo veri → geliştirme isteği örnek satır taşıyorsa (prompt'taki tablo/liste, ekran görüntüsü) ekran deploy_wizard'a SeedData: true ile gider (SeedFilePath = data/{ListFormCode}.json

    • SeedSync* açık, ListFormWizardDto.BuildListForm) ve satırlar aynı çağrıda seedRows ile configs/seeds/data/{ListFormCode}.json dosyasına yazılır (McpDevelopAppService; alan adları ekrana göre doğrulanır, Guid anahtarı sunucu üretir). Migrate ekranı kurar, ardından satırları ekler; MCP migrate'i beklemez. Talep döngüsü Testing'de biter, Staging'e kullanıcı alır. Dosya elle yazılmaz, görselden okunamayan değer uydurulmaz. SeedData wizard ekranında sorulmaz ama düzenlemede korunur (RefreshFrom + UI modeli). Ayrıntı: lowcode.instructions.md §0.10.1.
  • Talepteki görsel → App.DevRequests açıklamasına yapıştırılan görsel base64 data: URI olarak kalır, hiçbir yere yüklenmez. start_dev_request / get_dev_request onu metinden ayıklar (McpDevRequestImages) ve MCP resim içeriği (ImageContentBlock) olarak döndürür; metinde [image-N] kalır. Açıklama bu yüzden update_row ile yeniden yazılmaz. SubTasks de add_dev_request_subtasks ile yazılır: atanan kişi bağlanan kullanıcı, bitiş tarihi bugün — sunucu doldurur, modele bırakılmaz. Timesheets'i tamamen sunucu yazar (IMcpDevRequestTimesheetRecorder): get_dev_request/start_dev_request talebi etkin talep yapar, sonraki her üretim/veri yazma işlemi (create_table, deploy_wizard, delete_wizard, save_component, delete_component, save_report_category, save_report_template, delete_report_template, satır araçları, save_dev_request_analysis, append_dev_request_note, add_dev_request_subtasks) ona App.Mcp.Timesheet.* anahtarından Türkçe açıklama ve ölçülen süreyle bir satır ekler; yalnızca talebi açan start_dev_request satır yazmaz. Yeni bir üretim/veri yazma aracı eklenirse kaydedici çağrısı ve dil anahtarı da eklenir. Satır aracın UoW'si commit edildikten sonra ayrı UoW'de yazılır; aynı UoW'de yazmak Dapper transaction'ının Sas_H_DevRequest kilidini EF'in ayrı bağlantısından bekletip zaman aşımına düşürür.

  • Talep analizi → Description talebin yazıldığı hâliyle kalır; MCP ona yazmaz. Analiz ve üretim notları Analysis kolonundadır (Todo panosunda AnalysisExpr, Açıklama yanında sekme). Durum sırası Backlog → Analysis → Ready to Start → …: "Analiz et" save_dev_request_analysis ile yalnızca Backlog/Analysis talebini analiz edip Analysis'e alır (hiçbir şey üretmez), Ready to Start'a kullanıcı taşır, üretim notu append_dev_request_note ile Analysis'e eklenir. Analiz standardı tek yerdedir: lowcode.instructions.md §0.12; prompt'lar oraya yönlendirir.

  • MCP şema araçları → tablo/sütun bilgisi canlı katalogdan değil Sas_H_DbTableColumn sözlüğünden okunur (IMcpSchemaDictionary). Sözlüğün alan adları dbo.Sas_H_ListFormFieldsDictionaryJson prosedürünün ürettiği JSON ile birebir aynıdır; prosedür değişirse McpDictionaryListForm/McpDictionaryField de değişir. Sözlük tablo kataloğunu değil ekranların alanlarını döker: bir tablonun ekranda alan olarak tanımlanmamış kolonunu MCP göremez. Modelin okuması gereken kolon (ör. AbpUsers'ın kişisel bilgileri) ilgili ekranda gizli alan olarak durur.

  • MCP OAuth → bilinen sağlayıcılar (Claude, ChatGPT) seed edilmiş Platform_Mcp istemcisini, diğer MCP istemcileri /connect/register ile üretilen mcp_ önekli açık onaylı istemcileri kullanır (McpOAuthClientManager; seed McpOAuthClientDataSeedContributor). Bilinen sağlayıcı eklemek = DbMigrator'daki Platform_Mcp:RedirectUris listesine callback eklemek. MCP istemcisi kararı tek yerdedir (McpConsts.IsMcpClient): resource parametresi yok sayılır ve token kullanıcı oturumuna bağlanmaz; token ucuna yeni bir oturum yolu eklenirse bu istisna korunur, yoksa connector web oturumunu düşürür.

  • MCP yetki vermez → yeni bir MCP aracı kullanıcının uygulamadaki yetkisini aşamaz: liste verisi ekranın sorgusundan (IListFormSelectAppService/IListFormDataAppService) geçer; liste yetkisini aşan bir yetenek (ham SQL gibi) McpToolNames.RequiredPermissions'a uygulamadaki karşılık gelen yetkiyle eklenir. create_table bu yüzden .Write'a ek olarak App.SqlQueryManager ister; tablo scriptini tasarımcının generateCreateTableSql çıktısıyla aynı biçimde üretir (McpTableCreator) — tasarımcının şablon kolonları ya da script biçimi değişirse o da değişir.

  • Çalışan (persona) ekranları ve profil → çalışanın/yöneticinin kendi listeleri İK menüsüne konmaz: menüsüz App.Hr.My*/Team* wizard + Intranet panosu widget'ı. Profil sayfasının İzinlerim / Puantajım / Performansım sekmeleri persona ekranlarını salt okunur gösteren çekirdek bileşenlerdir (düzenle/sil yok, tek aksiyon izin talebi oluşturma); yeni persona ekranını profile eklemek kod + rota değişikliğidir (onay). Çalışan Bilgileri sekmesi ve Genel sekmesi kullanıcının kendi alanlarını my-profile ek alanlarından okur (PlatformProfileAppService.ProfileExtraProperties); sağlık, ücret ve işten çıkış alanları bu listeye girmez. Ayrıntı: lowcode.instructions.md §0.12.1.

  • Bildirim kapsamı → bildirim tipi, kuralı ve kuyruğu kiracıya aittir (Adm_T_NotificationTemplate, Adm_T_NotificationType, Adm_T_NotificationRule, Adm_T_Notification; dördü de IMultiTenant) ve ekranların tamamı Administration › Bildirimler altındadır. Ayrı bir manuel gönderim tablosu yoktur: Bildirimler ekranının ekleme formu elle gönderimdir, uç alıcı × kanal açılımını kuyruğa yazar (list-form-dynamic-api/notification-insert). Yeni kiracıya varsayılan tür/kural kümesi gelir: kendi veritabanı olan kiracı seed dosyasından alır, host veritabanını paylaşana NotificationTenantDefaultsSeedContributor aynı dosyalardan yeni kimliklerle kopyalar; iki yol aynı kiracıda çalışmaz. Kiracı verisine dokunan zamanlanmış işin Scope alanı Tenant ya da Host ve Kiracılar olmalıdır — Host kapsamında çalışan bir iş kiracı satırlarını göremez (WorkerScopeEnum, PlatformBackgroundWorker).

  • Bildirim bayrakları → sıra değişmez: IsSent (iletildi) olmadan IsRead yazılmaz, IsRead ile ReadTime birlikte yazılır. UiActivity kayıt oluşurken iletilmiş sayılır, UiToast/Desktop tarayıcıda gösterildiğinde (NotificationChannels.BrowserChannels; sent-many ucu yalnızca bu kümeyi işaretler), kalan kanalları NotificationWorker işaretler — gönderim okuma değildir. Kullanıcı tercihi (IsCustomized + RecipientType = User) kural değildir, yalnızca sahibini kapatır; alıcıyı kurallar belirler. Yeni bildirim üretildiği anda /notificationhub ile duyurulur (INotificationRealtimeNotifier; sözleşme bildirim modülünün Domain'inde, SignalR gerçeklemesi barındıran uygulamada — modül katmanı ASP.NET Core'a bağlanmaz) — duyuru en iyi çabadır, istemcideki yedek yoklama kaldırılmaz. Ayrıntı: README.md §13.3–§13.5.

  • SEO → ayrı bir SEO tablosu yoktur; meta, yolu tanımlayan kaydın üzerinde yaşar. Üç parça birlikte çalışır: genel varsayılanlar App.SiteManagement.Seo.* ayarları, yol bazlı alanlar Route.Seo* ve CustomComponent.Seo* (ikisinin de SeedSync* bayrakları açık), istemci tarafı SeoHead. Üçü de uygulama konfigürasyonunun seo alanından beslenir (PlatformApplicationConfigurationContributor → SeoAppService.GetConfigAsync); sitemap.xml ve robots.txt aynı servisten SeoController ile yayınlanır. Aynı yolu iki kaynak da tanımlıyorsa bileşen kazanır — çalışma zamanında da bileşen fiziksel rotayı gölgeler. Ziyaretçiye açık yeni bir yol üreten her iş SEO alanlarını da doldurur (standing default 7). Sitemap'e giren kaynaklar (Pub menüleri, aktif custom component rotaları, yayınlanmış blog yazıları) SeoAppService içindedir; yeni bir public kaynak türü eklenirse toplayıcıya da eklenir, yoksa sayfa sitemap'te görünmez. Yeni bir meta alanı = iki entity + SeoRouteDto + SeoFields (seeder) + SeoHead birlikte.

  • Yeni bir dil anahtarı → configs/seeds/data/App.Languages.LanguageText.json (EN + TR iki satır; Id = Sas_H_LanguageText:{ResourceName}:{Key}:{CultureName} adından URL isim alanlı v5 GUID); eklemeden önce anahtarın zaten var olup olmadığını kontrol et.

  • Custom component sınıfları → bileşen kodu veritabanında yaşar, Tailwind derlemede yalnızca seed dosyasını görür. Seed dosyası ui/ dışında olduğu için aynı sınıflar ui/safelist.txt sonuna da yazılır (twSafelistGenerator, derlemede otomatik); yoksa uygulama sunucuda stilsiz kalır. İkinci bir safelist dosyası açma.

  • Custom component kaydı/silinmesi → data/App.DeveloperKit.CustomComponents/{Id}.json (Custom Components ekranının seed verisi); ekran klasör modundadır, her bileşen kendi satır dosyasındadır. Hem listeden hem Visual Designer'dan (CustomComponentAppService) yapılan kayıt aynı klasöre yansır; kullandığı endpoint'ler crud/{Entity}.json.

  • Wizard dosyası → tek başına taşınmaz; export zip'i özyinelemeli bağımlılık kapanışını taşır (WizardExportCollector): alt formların wizard dosyaları, ekranın SeedFilePath veri aynası, lookup sorgularının işaret ettiği nesnelerin scriptleri, crud/ ve {sql|postgres}/{object|execute}/ dosyaları. Custom yolunda data/ girdisi yalnızca ilgili bileşen ve bağımlılık zincirine süzülür; import'ta hedef dosyanın üstüne yazılmaz, satırlar anahtar üzerinden birleştirilir. Kapanışa yeni bir bağımlılık türü eklenirse WizardImportValidator'daki karşılığı da eklenir — biri toplar, diğeri eksikliğini yakalar.

  • Talep bağı (DevRequest) → MCP deploy_wizard wizard dosyasına etkin talebi (IMcpDevRequestTimesheetRecorder.GetActiveRequestIdAsync) DevRequest { Id, Title } olarak yazar; bağ modelin girdisinden gelmez (ListFormWizardDto.DevRequestId [JsonIgnore]). Elle yapılan kayıt önceki bağı korur, talepsiz wizard'da bölüm hiç yazılmaz. Wizard Manager talebi gösterir, kaydına bağlar ve talebin bütün wizard'larını tek kapanışta export eder (export-dev-request; WizardExportCollector.CollectAsync birden çok dosya alır). Wizard dosyasını yeniden yazan yeni bir yol eklenirse DevRequest korunur — dosyayı DTO olarak okuyup yazan yollar bunu kendiliğinden yapar.

  • Şema değişikliği → ayrım kayıp riskine göredir. Eksik kolon eklenir (kayıpsız; ALTER TABLE ADD), var olan kolon hiçbir zaman değiştirilmez, hiçbir kolon düşürülmez, hiçbir tablo DROP edilmez. Ölçü farkında (uzunluk/kesinlik/nullability) import durdurulur ve fark somut olarak bildirilir; varsayılanı olmayan NOT NULL bir kolonun dolu tabloya eklenmesi de durdurulur — hangi değerin yazılacağı iş kararıdır. Karşılaştırma tek yerdedir (SqlTableSchemaComparer), analiz ve kapanış aynı sonucu kullanır. Karşılaştırmada tip adı kullanılmaz — sağlayıcı katalogları farklı kanonik ad raporlar ve metin karşılaştırması sahte fark üretir. Genel kural: sağlayıcıya göre değişen bir değeri metin olarak karşılaştırma, katalogdan dinamik oku.

  • Veritabanına dokunan her import ucu → EnsureImportNotBlocked ile başlar. Analizdeki engelleme kararı sunucuda blocked dosyasında durur; yeni bir uç eklenirse kontrolü de eklenir, yoksa karar istemci tarafından atlanabilir hâle gelir.

  • Wizard import'u → analiz dosyalar yazılmadan önce doğrular (WizardImportValidator); Error seviyesinde bir bulgu varsa oturuma blocked işareti düşer ve ApplyImport reddeder. Kapanış (CompleteImport) bağımlılık sırasıyla çalışır: sql/object → sql/execute → crud → data → wizard. Tablo eksikse önce scripti çalışır; sıra değiştirilmez. Ekran import'ta kurulmaz: wizard dosyasına Pending yazılır ve ekran deploy'daki gibi DB Migrate'te kurulur; dosyadaki ListForm/Fields sözleşmesi olduğu gibi kullanılır, wizard cevaplarından yeniden türetilmez — türetmek tasarımcı değişikliklerini silerdi. Wizard'ın menü/yetki/dil satırlarını taşıyan platform dosyaları data adımında uygulanmaz (WizardSeedRowStore.ListFormCodes). Engelleyici bulgu iki sınıftır: arşiv kaynaklı (RequiresReexport — bozuk/eksik dosya, karşılığı olmayan referans) hedef ortamda giderilemez, export tekrarlanır; ortam kaynaklı (eksik tablo, şema farkı) ekrandan giderilir. Yeni bir Error eklerken hangisi olduğu işaretlenir. Engelleme oturum genelidir, dosya bazlı değildir — import ya bütün olarak çalışır ya da hiç. Bunun bedeli, her engelin ekran içinde çözülebilir olmasıdır: tabloya dair bir bulgu üretirken TableName (+ tablo hedefte yoksa TableMissing) doldurulur, istemci tablo tasarımcısını buna göre açar.

  • ListForm.SeedFilePath dolu bir ekran → SeedSyncInsert/SeedSyncUpdate/SeedSyncDelete bayraklarından işaretli olan işlemler seed klasöründeki hedefe yansır (ListFormSeedDataSynchronizer); yalnızca host bağlamındaki kayıt yansır, kiracının kaydı veritabanında kalır. Hedef iki biçimdedir ve ayrım uzantıdadır: .json ile biten değer tek dosyadır (data/{ListFormCode}.json, bütün satırlar orada), uzantısız değer klasördür (data/{ListFormCode}/{anahtar}.json, her satır kendi dosyasında) ve klasör yoksa ilk kayıtta oluşturulur. Klasör modu satırı büyük ya da sık yazılan ekranlar içindir (varsayılan tek dosya; şu an klasör kullanan tek ekran Custom Components). Applier data/ altını özyinelemeli tarar ve bir klasörün tamamını tek unit of work'te uygular — ekran tanımı, alan listesi ve veri kaynağı satır dosyası başına değil ekran başına bir kez çözülür; dosyadaki ListFormCode boşsa ekran kodu klasör adından gelir. Tek dosyaya bağlı eski bir kayıt, klasörü diskte varsa migrate sırasında klasöre taşınır. Migrate/seed sırasında ve wizard import'unun kapanışında (CompleteImport) ListFormSeedDataApplier dosyayı geri uygular. Migrate/seed'de kayıt yoksa eklenir, varsa dokunulmaz; import'ta overwriteExisting açıktır: var olan kayıt dosyadaki değerlerle güncellenir ve soft delete izleri (IsDeleted/DeletionTime/DeleterId) temizlenir. Satırı veritabanından okuyan senkronizasyon açık UoW varsa commit sonrasında kendi UoW'sinde çalışır (EF ile yazan çağıran, ör. MenuAppService, aksi hâlde eski değeri dosyaya yazardı); yalnızca dosyaya dokunan RemoveRowsByFieldAsync hemen çalışır. Yeni bir kayıt yolu (yeni endpoint, toplu işlem, tipli AppService) eklenirse senkronizasyon çağrısı da eklenir; yoksa dosya bayatlar.

  • Seed kapsamı → configs/seeds/ tek klasördür; host/ ya da tenants/{tenantId}/ yoktur. Host ve kendi veritabanı olan her tenant aynı dosyalarla kurulur, host veritabanını paylaşan tenant'ta dosyalar yeniden uygulanmaz (SeedFileScope; migrate kararı DataSeedContext'e yazar). Dosyayı okuyan yeni bir seeder SeedFileScope.IsEnabled ile başlar. Dosyalar yalnızca host bağlamında yazılır; dosyaya yazan yeni bir yol tenant bağlamında yazmaz. Bekleyen wizard kurulumu (Pending) dosyaları uygulayan son veritabanında kapanır (SeedFileScope.CompletesPending). Tek istisna {sql|postgres}/host/{object,execute}: yalnızca host'ta çalışır; tenant veritabanında karşılığı olmayan nesneye (HangFire tabloları) dayanan script buraya konur, tablo önekine bakılmaz. Kendi veritabanı olan tenant'ta EF ile yalnızca IMultiTenant varlıklar o veritabanına gider; ListForm, menü ve yetki tanımını ABP her zaman host'ta okur. Kiracı tablosunun satırları kiracı adıyla kayıtlı DataSource üzerinden gider. Kiracı yalnızca kimliğiyle değiştirildiğinde ad boş kalır; bu durumda adı DataSourceManager kiracı kaydından okur. Yeni tenant yöneticisi ilk seed'de kiracı tarafı bütün yetkileri alır (PlatformIdentityDataSeeder). Bunun nedeni, DbMigrator'da dinamik yetki deposunun kapalı olmasıdır: ABP'nin yetki seed'i seed dosyasından gelen yetkileri göremez.

  • Seed dosyası yazmak → veri dosyası ListFormSeedDataFileDto.Serialize, wizard dosyası WizardSeedFileDto.ToSeedJson ile yazılır; düz JsonSerializer.Serialize + File.WriteAllText kullanılmaz. System.Text.Json emojiyi her zaman vekil çift kaçışına çevirir; dosya baştan yazıldığında bütün emojiler sahte fark üretir. İkisi de SeedJsonText.RestoreSurrogatePairs'ten geçer. Veri dosyasının GeneratedAt değeri oluşturma anıdır: Serialize yalnızca boşsa doldurur, hiçbir yazma yolu onu güncellemez — ekleyip geri kaldıran işlem (wizard deploy + silme) dosyayı birebir önceki hâline döndürür. crud/ dosyalarının damgası ayrıdır ve karar verir (aşağıda).

  • crud/{Entity}.json → uygulama kuralı CrudEndpointSeedApplier'dadır ve damga karşılaştırmalıdır: eksik endpoint eklenir, mevcut kayıt yalnızca dosyanın GeneratedAt değeri kaydın LastModificationTime/CreationTime değerinden yeniyse güncellenir/silinir. Migrate kullanıcının runtime revizyonunu ezmez. Yeni bir seeder yazarken bu kuralı taklit et: migrate varsayılanı eklemektir, ezmek değil.

  • Modül = menü = tablo öneki = yetki kökü → kök menüler Sas (App.Saas), Adm (App.Administration), Hr (App.Hr, İK) ve Pub modüllerindedir. Bir ekranı modüller arası taşımak birlikte yapılır: TableNameResolver (MenuPrefix) + AppCodes (Seed/Platform) + ListFormSeeder_{Saas,Administration,Hr}.cs + data/{ListFormCode}.json adı + menü/yetki/dil satırları + route Authority. Yetki Modules değeri: Saas/Administration Sas|Adm, İK Sas|Hr (Sas host, Adm/Hr kiracı görünürlüğü). İK departman/pozisyon BranchId zorunludur; seed satırı host şubesine (PlatformConsts.Branches.BranchIdDefaultValue) bağlanır.

  • Menü ağacı ve yetkiler → configs/seeds/data/App.Menus.{Routes,Module,Menu}.json ve Abp.Identity.Permission{Groups,s}.json; platformun kendi menü/yetki kayıtları da diğer liste verisiyle aynı mekanizmadan (ListFormSeedDataApplier) geçer, ayrı bir seeder yoktur. Order alanı bağımlılık sırasını kurar: route → modül → menü, yetki grubu → yetki, ülke grubu → ülke → şehir → ilçe (App.Definitions.{CountryGroup,Country,City,District}.json), bildirim tipi → kuralı (App.Notifications.Notification{Types,Rules}.json). Para birimi, hitap, AI bot (App.Definitions.{Currency,ContactTitle,AiBot}.json), ayar tanımları (App.SettingDefinitions.json) ve zamanlanmış işler (App.BackgroundWorkers.RecurringJobs.json), dil → metin (App.Languages.{Language,LanguageText}.json; LanguagesData.json + LanguageDataSeeder kaldırıldı) da buradadır; HostData.json kaldırıldı, HostDataSeeder yalnızca bağlantı cümlesi appsettings'ten gelen varsayılan veri kaynağını kurar. Dosyalar ekranın anahtarını (KeyFieldName) kullanır; Guid anahtarlı satırın Id'si iş anahtarından türetilmiş sabit bir GUID'dir, satır eklerken aynı kuralla üret. App.Menus.Routes.json dosyasına yalnızca var olan satırın Seo* alanları için dokunulur (standing default 7); rota eklemek, silmek ya da yolunu değiştirmek çerçeve dışıdır.

  • Public site → menü Menu tablosunda ModuleId = "Pub" altında (PlatformConsts.PublicSite); App.Public.Header hem üst menüyü hem alt bilgideki hızlı bağlantıları besler (ikisi de PublicNav düğümü) ve ziyaretçiye uygulama konfigürasyonunun publicMenus alanıyla taşınır (menü ucu [Authorize]). Sayfa ise Custom Component'tir: /admin/ ile başlamayan RoutePath otomatik public route olur ve aynı yolu paylaşan fiziksel route'u gölgeler; RoutePath boş bırakılabilir (rotasız bileşen). Yeni public sayfa = data/App.DeveloperKit.CustomComponents/{Id}.json satırı + Pub menü kaydı; ikisi birlikte yazılır. Üst ve alt bilgi rotasız bileşenlerdir (PublicHeader, PublicFooter), layout ikisini de renderComponent ile çizer; rotasız bileşende RoutePath NULL yazılır (tekil indeks yalnızca dolu değerleri kapsar). Üst bilginin davranışı components/publicSite katalog düğümlerinde, yerleşimi tasarımcıdadır. Public bileşenlerin hepsi tasarımcı belgesidir (Props.visualDesigner, sourceMode: visual); kod belgeden üretilir (generateDesignerCode), elle yazılmış koda dönülmez. Runtime kapsamında react-icons/react-router yoktur — ikon PlatformIcon düğümü, gezinme a düğümü. Kanvasın doğrudan çizdiği katalog düğümleri tek sözlüktedir (componentEditor/runtimeNodes.ts); yeni bir düğüm eklerken bileşen + bu sözlük + catalog.ts tanımı + dil anahtarı birlikte yazılır. Elle yazılmış bir bileşen tasarımcıda kod modunda açılır, kanvasa çevrilmez. Ayrıntı: README.md §10.4.

  • Çok değerli alan (dxTagBox / çoklu dxGridBox) → tek metin kolonunda | ile saklanır. Ekran varsayılan list-form-data/* ucundan kaydediyorsa birleştirmeyi QueryHelper yapar; tipli DTO alan bir uca (list-form-dynamic-api/..., Custom Endpoint, Dynamic Service) bağlıysa DTO alanına [JsonConverter(typeof(MultiValueStringJsonConverter))] eklenir — yoksa istek 400 ile düşer. Ayrıntı: lowcode.instructions.md §4.3.2.1.

Definition of done

  • Ölü kod bırakma: kullanılmayan using, private üye, alan, DTO, hook, servis, tip. Bir tip silinmeden önce repo geneli (api + ui + configs) referans taraması yapılır; DI ile çözülen tipler düz metin aramasında referanssız görünür.
  • Yorum satırına alınmış kod bloğu bırakılmaz.
  • Modül/ekran/bileşen ekleyen ya da davranışını değiştiren iş modül dokümanını (configs/seeds/docs/modules/{Modül}.md) yazar veya günceller; liste ekranının açıklaması ayrıntılıdır (standing default 14–15).
  • api/ değişikliğinden sonra: dotnet build -p:EnforceCodeStyleInBuild=true — 0 hata, yeni IDE00xx yok.
  • ui/ değişikliğinden sonra: npm run typecheck ve npm run lint.
  • Yorumlar nedeni anlatır, ne yaptığını değil. Kod zaten ne yaptığını söylüyor.
  • Dokümantasyon kodla birlikte güncellenir. Platformun davranışını değiştiren her değişiklik (yeni/kaldırılan seeder, seed klasörü ya da dosya biçimi, ListForm sözleşmesine alan ekleme, yeni yetki/dil anahtarı, katman sınırı, yeni mekanizma) aynı commit içinde ilgili .md dosyasına yansıtılır: README.md (ne nerede yaşıyor, klasör düzeni, tablolar), .github/instructions/lowcode*.instructions.md (artefakt/yetenek sözleşmesi), dotnet.instructions.md (kod standardı, katman kuralı), CLAUDE.md (çalışma kuralı). Değişiklik hangi md'de karşılığı olduğu bilinmiyorsa önce grep ile eski anlatım aranır ve düzeltilir; bayat kalan bir satır bırakma. Doküman güncellemesi "sonraya" bırakılmaz.

Tone

  • Türkçe soruya Türkçe cevap.
  • Platformu tanıtma, mimariyi özetleme, ne yapacağını anlatıp sonra bir de ne yaptığını özetleme.
  • Cevabın boyutu sorunun boyutu kadar olsun.