@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.env.example +8 -3
  2. package/.env.production.example +2 -0
  3. package/README.md +229 -193
  4. package/dist/config.js +20 -2
  5. package/dist/knowledge/backendConstraints.js +897 -0
  6. package/dist/knowledge/componentCatalog.js +196 -42
  7. package/dist/knowledge/coverage.js +43 -0
  8. package/dist/knowledge/flowGuards.js +30 -0
  9. package/dist/knowledge/guideSections.js +49 -0
  10. package/dist/knowledge/hydrate.js +97 -0
  11. package/dist/knowledge/languageCodes.js +36 -0
  12. package/dist/knowledge/languages.js +105 -0
  13. package/dist/knowledge/layoutSummary.js +10 -0
  14. package/dist/knowledge/localization.js +51 -0
  15. package/dist/knowledge/normalize.js +449 -16
  16. package/dist/knowledge/outlines.js +136 -12
  17. package/dist/knowledge/propTypes.js +80 -0
  18. package/dist/knowledge/rules.js +618 -43
  19. package/dist/knowledge/schemaGuard.js +199 -0
  20. package/dist/knowledge/styleResolve.js +63 -4
  21. package/dist/observability/trace.js +27 -3
  22. package/dist/platform/errors.js +46 -4
  23. package/dist/platform/http.js +33 -4
  24. package/dist/platform/patch.js +27 -0
  25. package/dist/platform/refs.js +104 -0
  26. package/dist/platform/responses.js +9 -2
  27. package/dist/platform/screenshot.js +527 -16
  28. package/dist/platform/secrets.js +40 -0
  29. package/dist/platform/tokenVault.js +89 -0
  30. package/dist/platform/writeDiff.js +281 -0
  31. package/dist/resources/examples/layout-app-shell.json +1 -1
  32. package/dist/resources/examples/microservice-table-list.json +2 -4
  33. package/dist/resources/examples/page-dynamic-form.json +2 -2
  34. package/dist/resources/examples/page-flex-sections.json +99 -0
  35. package/dist/resources/examples/page-sign-in.json +3 -2
  36. package/dist/resources/guides/component.md +69 -8
  37. package/dist/resources/guides/entity.md +404 -16
  38. package/dist/resources/guides/expressions.md +44 -2
  39. package/dist/resources/guides/external-component.md +9 -0
  40. package/dist/resources/guides/getting-started.md +77 -7
  41. package/dist/resources/guides/layout.md +53 -10
  42. package/dist/resources/guides/microservice.md +226 -13
  43. package/dist/resources/guides/middleware.md +89 -1
  44. package/dist/resources/guides/page.md +383 -46
  45. package/dist/resources/guides/translation.md +126 -8
  46. package/dist/resources/guides/view.md +16 -1
  47. package/dist/resources/guides/workflow.md +198 -27
  48. package/dist/resources/knowledge.js +5 -0
  49. package/dist/resources/rules.js +27 -0
  50. package/dist/resources/schema-fingerprint.json +8 -0
  51. package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
  52. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  53. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  54. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  55. package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
  56. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  57. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  58. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  59. package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
  60. package/dist/resources/schemas/GetRowsQuery.json +3 -0
  61. package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
  62. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
  64. package/dist/resources/schemas/StepDto.json +23 -3
  65. package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
  66. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  67. package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
  68. package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
  69. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  70. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  71. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  72. package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
  73. package/dist/server.js +4 -0
  74. package/dist/tools/access.js +3 -3
  75. package/dist/tools/applications.js +1 -1
  76. package/dist/tools/automations.js +63 -30
  77. package/dist/tools/common.js +39 -0
  78. package/dist/tools/components.js +16 -7
  79. package/dist/tools/datasources.js +83 -21
  80. package/dist/tools/deployments.js +34 -2
  81. package/dist/tools/dtos.js +25 -12
  82. package/dist/tools/entities.js +113 -28
  83. package/dist/tools/externalComponents.js +9 -3
  84. package/dist/tools/files.js +20 -6
  85. package/dist/tools/flows.js +110 -11
  86. package/dist/tools/folders.js +7 -2
  87. package/dist/tools/globalFunctions.js +63 -0
  88. package/dist/tools/layouts.js +103 -29
  89. package/dist/tools/microservices.js +229 -38
  90. package/dist/tools/organizations.js +5 -6
  91. package/dist/tools/pages.js +365 -100
  92. package/dist/tools/platformInfo.js +152 -11
  93. package/dist/tools/projects.js +183 -33
  94. package/dist/tools/queries.js +32 -10
  95. package/dist/tools/rows.js +159 -45
  96. package/dist/tools/screenWrite.js +59 -0
  97. package/dist/tools/screens.js +28 -0
  98. package/dist/tools/translations.js +24 -8
  99. package/dist/tools/views.js +20 -10
  100. package/dist/tools/workflows.js +33 -16
  101. package/package.json +6 -3
@@ -1,10 +1,96 @@
1
1
  import { listKnowledgeFiles, readKnowledgeFile } from '../resources/knowledge.js';
2
+ import { splitGuideSections } from '../knowledge/guideSections.js';
3
+ import { PlatformApiError } from '../platform/errors.js';
2
4
  import { logCapabilityGap } from '../observability/trace.js';
3
5
  import { handle } from '../platform/responses.js';
4
6
  import { readOnly } from './common.js';
5
7
  import { z } from 'zod';
8
+ const severitySynonyms = {
9
+ blocker: 'blocker', blocking: 'blocker', critical: 'blocker', high: 'blocker', kritik: 'blocker', engel: 'blocker',
10
+ workaround: 'workaround', medium: 'workaround', major: 'workaround', moderate: 'workaround', orta: 'workaround',
11
+ cosmetic: 'cosmetic', low: 'cosmetic', minor: 'cosmetic', nit: 'cosmetic', düşük: 'cosmetic', kozmetik: 'cosmetic'
12
+ };
13
+ const goalAliases = ['goal', 'title', 'summary', 'what'];
14
+ const blockerAliases = ['blocker', 'reason', 'problem', 'missing', 'description'];
15
+ export function normalizeGapReport(input) {
16
+ const pick = (names) => {
17
+ for (const name of names) {
18
+ const value = input[name];
19
+ if (typeof value === 'string' && value.trim() !== '')
20
+ return { value: value.trim(), from: name };
21
+ }
22
+ return {};
23
+ };
24
+ const goal = pick(goalAliases);
25
+ const blocker = pick(blockerAliases);
26
+ if (goal.value === undefined && blocker.value === undefined) {
27
+ throw new PlatformApiError(`platform_gap_report en az bir metin ister: goal (eşanlamlı: ${goalAliases.slice(1).join(' · ')}) ya da blocker (eşanlamlı: ${blockerAliases.slice(1).join(' · ')}). severity zorunlu değildir; verilirse blocker | workaround | cosmetic (eşanlamlıları da kabul edilir).`);
28
+ }
29
+ const rawSeverity = typeof input.severity === 'string' ? input.severity.trim().toLowerCase() : undefined;
30
+ // ÖLÇÜLDÜ 2026-09-04 (benchmark 4. koşu): tanınmayan ya da eksik şiddet sessizce `workaround`
31
+ // oluyordu ve beş bildirimin beşi de orta kovaya düştü — aralarında blocker olması gereken biri
32
+ // vardı. Şiddet yol haritasındaki SIRAYI belirleyen alan; yazım hatasını sessizce kabul etmek,
33
+ // reddetmekten pahalı. Eksiklik ise kaydı düşürmez, yalnız görünür olur.
34
+ if (rawSeverity !== undefined && severitySynonyms[rawSeverity] === undefined) {
35
+ throw new PlatformApiError(`platform_gap_report '${rawSeverity}' diye bir şiddet tanımıyor. Geçerli: blocker | workaround | cosmetic (eşanlamlılar: ${Object.keys(severitySynonyms).join(' · ')}). Şiddet yol haritasındaki sırayı belirler; yanlış yazılan değer sessizce kabul edilmez.`);
36
+ }
37
+ const severity = rawSeverity === undefined ? 'workaround' : severitySynonyms[rawSeverity];
38
+ const normalizedFrom = {
39
+ ...(goal.from !== undefined && goal.from !== 'goal' ? { goal: goal.from } : {}),
40
+ ...(blocker.from !== undefined && blocker.from !== 'blocker' ? { blocker: blocker.from } : {}),
41
+ ...(rawSeverity !== undefined && rawSeverity !== severity ? { severity: rawSeverity } : {})
42
+ };
43
+ return {
44
+ goal: goal.value ?? '(belirtilmedi — yalnız engel yazıldı)',
45
+ blocker: blocker.value ?? '(belirtilmedi — yalnız hedef yazıldı)',
46
+ severity,
47
+ ...(typeof input.module === 'string' ? { module: input.module } : {}),
48
+ ...(Array.isArray(input.attempted) ? { attempted: input.attempted.filter((entry) => typeof entry === 'string') } : {}),
49
+ ...(typeof input.workaround === 'string' ? { workaround: input.workaround } : {}),
50
+ normalizedFrom,
51
+ incomplete: [
52
+ ...(goal.value === undefined ? ['goal'] : []),
53
+ ...(blocker.value === undefined ? ['blocker'] : []),
54
+ ...(rawSeverity === undefined ? ['severity'] : []),
55
+ ...(typeof input.module === 'string' && input.module.trim() !== '' ? [] : ['module'])
56
+ ]
57
+ };
58
+ }
59
+ const guideRouting = {
60
+ 'getting-started': "boş org'da sıfırdan kurulum sırası",
61
+ 'modify-existing': 'mevcut yapıyı bozmadan değiştirme disiplini',
62
+ workflow: 'DTO-önce akışı, branch bağlama, tip×step matrisi',
63
+ microservice: 'kaynak seçimi (table > query), selectFields disiplini',
64
+ entity: "alan tipleri, ilişkiler, hazır entity'ler",
65
+ page: 'sayfa/flow işleri; flex düzen prop grubu ve boyut modları (Kural 1f-2)',
66
+ layout: 'yerleşim kabuğu: tek slot, views, mobil yığılma, widthType fill/fixed/auto',
67
+ component: 'komponent modeli, olay sözleşmeleri, appearance boyut sözlüğü (widthString/heightString)',
68
+ html: 'hazır komponent yetmediğinde tek seferlik kaçış kapısı (Mustache, fireEvent, script kuralları)',
69
+ 'external-component': 'kalıcı ihtiyaçta kendi komponentini yazıp yayınlama',
70
+ middleware: 'auth/oturum/yetki',
71
+ view: 'tam metin arama indeksi',
72
+ expressions: 'ifade fonksiyon/bağlam sözlüğü (system.*, file.*, Page.*, state.*, Steps…)',
73
+ translation: 'sayfa/içerik çevirileri (çoklu dil)'
74
+ };
75
+ // page.md 123 KB; section'sız dönerse yanıt tool çıktı sınırını aşıyor ve rehber MCP kanalından
76
+ // HİÇ okunamıyor (27/05 E-1, 09-04 koşusu). Eşik ÖLÇÜLEN kopma noktasından türetilir, "bugün
77
+ // hangi rehber sığıyorsa" ondan değil: 40.000'de sabitken rehber her büyüdüğünde sessizce
78
+ // outline'a düşüyordu — entity.md 43 KB'de kimse karar vermeden kesiliyordu, workflow.md ise
79
+ // 2026-09-09 merge'ünde 40.284 karakterle tavanı 284 aştı. 48.000 hem ikisini tam bırakır hem
80
+ // kopma noktasının çok altındadır; page.md tek başına üstte kalır.
81
+ const oversizeGuideChars = 48000;
82
+ // Outline dalında gövde AYRICA kesilir. Bu sınır kanalda güvenli ölçülmüş yanıt boyutudur ve
83
+ // eşikten bağımsızdır: eşiği büyütmek page.md'nin kesilmiş yanıtını büyütmemeli.
84
+ const truncatedGuideBodyChars = 40000;
6
85
  export function registerPlatformTools(server, platform) {
7
86
  const config = platform.config;
87
+ const guides = listKnowledgeFiles('guides').map(file => file.replace(/\.md$/, ''));
88
+ const guideDescription = [
89
+ 'Modülün iş kuralları rehberini döner. Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun:',
90
+ ...guides.map(name => `- ${name}${guideRouting[name] ? `: ${guideRouting[name]}` : ''}`),
91
+ "Büyük rehberler (page ~120 KB) section'sız çağrıda TAM METİN DÖNMEZ: giriş + bölüm listesi (outline) döner, truncated: true olur. Gövdeyi section ile bölüm bölüm alın.",
92
+ 'Örnek gövdeler için ngapps://examples kaynaklarına bakın.'
93
+ ].join('\n');
8
94
  server.registerTool('platform_whoami', {
9
95
  title: 'Oturum bilgisi',
10
96
  description: 'Gateway\'in platformda kimin adına çalıştığını döner; auth kurulumunu doğrulamak için ilk çağrılacak tool.',
@@ -13,32 +99,87 @@ export function registerPlatformTools(server, platform) {
13
99
  }, handle(config, () => platform.org('GET', '/connect/userinfo')));
14
100
  server.registerTool('platform_guide', {
15
101
  title: 'Platform rehberi oku',
16
- description: "Modülün iş kuralları rehberini döner (workflow, microservice, entity, page, layout, component, html, middleware, view, expressions, translation, getting-started, modify-existing). Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun; sayfa/layout/flow işleri için page, yerleşim kabuğunun kendi kuralları (tek slot, views alanı, micro-op yokluğu, mobil yığılma) için layout, komponent modeli/olay sözleşmeleri için component, hazır komponentler yetmediğinde HTML kaçış kapısı (Mustache, fireEvent, script kuralları) için html, auth/oturum/yetki için middleware, tam metin arama indeksi için view, ifade yazarken kullanılabilir fonksiyon/bağlam sözlüğü (system.*, file.*, Page.*, state.*, Steps…) için expressions, sayfa/içerik çevirileri (çoklu dil) için translation rehberi. Örnek gövdeler için ngapps://examples kaynaklarına bakın.",
102
+ description: guideDescription,
17
103
  inputSchema: {
18
- guide: z.string().describe('Rehber adı; bilinmeyen ad verilirse mevcut rehberler listelenir')
104
+ guide: (guides.length > 0 ? z.enum(guides) : z.string())
105
+ .describe('Rehber adı; bilinmeyen ad verilirse mevcut rehberler listelenir'),
106
+ section: z.string().optional()
107
+ .describe("Yalnız bu bölümü döner (yanıttaki 'sections' listesinden bir slug). Verilmezse rehber tam döner; ancak tool yanıt sınırını aşan büyük rehberlerde (page) yalnız giriş + outline döner")
19
108
  },
20
109
  annotations: readOnly
21
- }, handle(config, async ({ guide }) => {
110
+ }, handle(config, async ({ guide, section }) => {
22
111
  const available = listKnowledgeFiles('guides').map(file => file.replace(/\.md$/, ''));
23
112
  if (!available.includes(guide)) {
24
113
  return { error: `'${guide}' diye bir rehber yok.`, available };
25
114
  }
26
- return { guide, content: readKnowledgeFile('guides', `${guide}.md`), examples: listKnowledgeFiles('examples').map(file => `ngapps://examples/${file.replace(/\.json$/, '')}`) };
115
+ const markdown = readKnowledgeFile('guides', `${guide}.md`);
116
+ const parts = splitGuideSections(markdown);
117
+ const sections = parts.map(part => part.slug);
118
+ const examples = listKnowledgeFiles('examples').map(file => `ngapps://examples/${file.replace(/\.json$/, '')}`);
119
+ if (section === undefined) {
120
+ if (markdown.length > oversizeGuideChars) {
121
+ // Bölümleme başarısızsa parts[0] TÜM rehberdir; kesmeden döndürmek yanıtı yine tool
122
+ // sınırının üstüne çıkarır (ölçüldü 2026-09-05: CRLF çalışma ağacında page.md 124 KB
123
+ // döndü ve okunamadı). Gövde her koşulda kesilir, bölümleme çökerse ayrıca işaretlenir.
124
+ const degraded = parts.length < 2;
125
+ return {
126
+ guide,
127
+ section: null,
128
+ sections,
129
+ outline: parts.map(part => ({ slug: part.slug, title: part.title, chars: part.body.length })),
130
+ content: (parts[0]?.body ?? '').slice(0, truncatedGuideBodyChars),
131
+ truncated: true,
132
+ ...(degraded ? { degraded: true } : {}),
133
+ note: degraded
134
+ ? `'${guide}' rehberi ${markdown.length} karakter ve '## ' başlıklarına AYRILAMADI — bölümlü okuma çalışmıyor ve gövde ${truncatedGuideBodyChars} karaktere kesildi. Tam metni ngapps://guides/${guide} kaynağından okuyun ve bunu platform_gap_report ile bildirin: bu bir gateway kusurudur.`
135
+ : `'${guide}' rehberi ${markdown.length} karakter — tek tool yanıtına sığmaz, gövdesi kesildi. Yukarıdaki outline'dan bir slug seçip platform_guide('${guide}', section) ile okuyun. Tam metin gerekiyorsa ngapps://guides/${guide} kaynağını kullanın.`,
136
+ examples
137
+ };
138
+ }
139
+ return { guide, section: null, sections, content: markdown, examples };
140
+ }
141
+ const found = parts.find(part => part.slug === section);
142
+ if (found === undefined) {
143
+ return { error: `'${guide}' rehberinde '${section}' diye bir bölüm yok.`, guide, sections };
144
+ }
145
+ return { guide, section: found.slug, sections, content: `## ${found.title}\n\n${found.body}`, examples };
27
146
  }));
147
+ // Boşluk bildirimi benchmark'ın taşıyıcı mekanizmasıdır: ilk çağrının şema hatasıyla düşmesi
148
+ // ölçümün kendisini kaybettiriyordu (09-02 G-1). Alan adları ve severity sözlüğü bu yüzden
149
+ // hoşgörülüdür; çeviri yapıldığında yanıt bunu normalizedFrom ile söyler.
28
150
  server.registerTool('platform_gap_report', {
29
151
  title: 'Kabiliyet boşluğu bildir',
30
152
  description: "Yapmak İSTEDİĞİNİZ ama platform desteklemediği için yapamadığınız her noktada BUNU ÇAĞIRIN — tasarımı sessizce küçültmeyin. Bu çağrı platforma gitmez, yalnızca günlüğe 'KABİLİYET BOŞLUĞU' bloğu yazar; hata günlüğünde görünmeyen bu kayıtlar ürün yol haritasının girdisidir. Önce rehbere bakıp (platform_guide; hazır komponent yetmiyorsa tek seferlik için 'html' kaçış kapısı, kalıcı bir ihtiyaç içinse 'external-component' — kendi komponentinizi yazıp yayınlayabilirsiniz) gerçekten desteklenmediğinden emin olun: desteklenen bir şeyi 'yok' diye bildirmek yanlış yol haritası üretir.",
31
153
  inputSchema: {
32
- goal: z.string().min(1).describe('Ne yapmak istediniz — kullanıcıya görünen sonuç olarak yazın ("randevu listesini hasta adına göre süzmek")'),
33
- blocker: z.string().min(1).describe('Neden yapamadınız — hangi kabiliyetin eksik olduğu ("uçta X yok", "komponentte Y prop\'u yok")'),
34
- severity: z.enum(['blocker', 'workaround', 'cosmetic']).describe('blocker: özellik hiç yapılamadı · workaround: daha kötü bir yolla yapıldı · cosmetic: yalnız görsel/ergonomik kayıp'),
154
+ goal: z.string().min(1).optional().describe('Ne yapmak istediniz — kullanıcıya görünen sonuç olarak yazın ("randevu listesini hasta adına göre süzmek"). Eşanlamlı kabul edilir: title · summary · what.'),
155
+ blocker: z.string().min(1).optional().describe('Neden yapamadınız — hangi kabiliyetin eksik olduğu ("uçta X yok", "komponentte Y prop\'u yok"). Eşanlamlı kabul edilir: reason · problem · missing · description.'),
156
+ severity: z.string().optional().describe("YOL HARİTASINDAKİ SIRAYI BU ALAN BELİRLER, doldurun. blocker: özellik hiç yapılamadı · workaround: daha kötü bir yolla yapıldı · cosmetic: yalnız görsel/ergonomik kayıp. Eşanlamlılar çevrilir (critical/high/blocking→blocker, medium/major→workaround, low/minor/nit→cosmetic). TANINMAYAN değer reddedilir; verilmezse kayıt düşer ama yanıt 'incomplete' ile uyarır ve bildirim orta kovaya yazılır."),
35
157
  module: z.string().optional().describe('İlgili modül: entity | microservice | workflow | page | component | middleware | view | flow'),
36
158
  attempted: z.array(z.string()).optional().describe('Denenen tool/yaklaşımlar — boşluğun gerçekten araştırıldığını gösterir'),
37
- workaround: z.string().optional().describe('Bunun yerine ne yaptınız; hiçbir şey yapılamadıysa boş bırakın')
159
+ workaround: z.string().optional().describe('Bunun yerine ne yaptınız; hiçbir şey yapılamadıysa boş bırakın'),
160
+ title: z.string().optional().describe('goal ile aynı alan (eşanlamlı)'),
161
+ summary: z.string().optional().describe('goal ile aynı alan (eşanlamlı)'),
162
+ what: z.string().optional().describe('goal ile aynı alan (eşanlamlı)'),
163
+ reason: z.string().optional().describe('blocker ile aynı alan (eşanlamlı)'),
164
+ problem: z.string().optional().describe('blocker ile aynı alan (eşanlamlı)'),
165
+ missing: z.string().optional().describe('blocker ile aynı alan (eşanlamlı)'),
166
+ description: z.string().optional().describe('blocker ile aynı alan (eşanlamlı)')
38
167
  },
39
168
  annotations: readOnly
40
- }, handle(config, async ({ goal, blocker, severity, module, attempted, workaround }) => {
41
- logCapabilityGap({ goal, blocker, severity, module, attempted, workaround });
42
- return { recorded: true, severity, module: module ?? null, note: 'Günlüğe yazıldı. Kullanıcıya da tek cümleyle söyleyin: neyi yapamadınız ve yerine ne yaptınız.' };
169
+ }, handle(config, async (input) => {
170
+ const gap = normalizeGapReport(input);
171
+ logCapabilityGap({ goal: gap.goal, blocker: gap.blocker, severity: gap.severity, module: gap.module, attempted: gap.attempted, workaround: gap.workaround });
172
+ return {
173
+ recorded: true,
174
+ severity: gap.severity,
175
+ module: gap.module ?? null,
176
+ ...(Object.keys(gap.normalizedFrom).length > 0 ? { normalizedFrom: gap.normalizedFrom } : {}),
177
+ ...(gap.incomplete.length > 0 ? { incomplete: gap.incomplete } : {}),
178
+ note: gap.incomplete.length > 0
179
+ ? `Günlüğe yazıldı ama eksik: ${gap.incomplete.join(', ')}.`
180
+ + (gap.incomplete.includes('severity') ? ` Şiddet verilmediği için kayıt '${gap.severity}' olarak düştü — yol haritasındaki sırayı bu alan belirler, blocker'sa bunu açıkça yazın.` : '')
181
+ + ' Bir sonraki bildirimde bu alanları da doldurun — eksik kayıt yol haritasında sırasını kaybeder. Kullanıcıya da tek cümleyle söyleyin: neyi yapamadınız ve yerine ne yaptınız.'
182
+ : 'Günlüğe yazıldı. Kullanıcıya da tek cümleyle söyleyin: neyi yapamadınız ve yerine ne yaptınız.'
183
+ };
43
184
  }));
44
185
  }
@@ -1,7 +1,55 @@
1
1
  import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
+ import { normalizeLanguageCode, normalizeLanguageCodes } from '../knowledge/languageCodes.js';
2
3
  import { projectCoverageGaps } from '../knowledge/coverage.js';
4
+ import { withNormalization } from '../knowledge/normalize.js';
5
+ import { PlatformApiError } from '../platform/errors.js';
3
6
  import { handle } from '../platform/responses.js';
4
7
  import { z } from 'zod';
8
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
9
+ import { policyRules } from '../knowledge/rules.js';
10
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
11
+ import { readProjectLanguages, runtimeResolvesTranslations, resolveSeedLanguages, SEED_LANGUAGES, SEED_DEFAULT_LANGUAGE } from '../knowledge/languages.js';
12
+ const languageCode = z.string().regex(/^[a-z]{2}$/, 'ISO 639-1 iki harfli küçük dil kodu (tr, en, de …)');
13
+ function localizationFix(state) {
14
+ return state.languages.length === 0
15
+ ? "project_update(id, languages: ['en','tr'], defaultLanguage: 'en') — kullanacağınız dilleri yazın"
16
+ : `project_update(id, defaultLanguage: '${state.languages[0]}')`;
17
+ }
18
+ /**
19
+ * Eksikliği `null` ile anlatamayız: `prune` (platform/responses.ts) null ve undefined alanları her
20
+ * yanıttan siliyor, yani `defaultLanguage: null` yazsak gövdeden düşerdi — bu bloğun düzeltmek
21
+ * için var olduğu görünmezliğin ta kendisi. Eksik olan ne varsa `missing` dizisinde ADIYLA durur;
22
+ * boş dizi ve `false` prune'dan sağ çıkar.
23
+ */
24
+ function localizationBlock(state) {
25
+ const resolves = runtimeResolvesTranslations(state);
26
+ const missing = [
27
+ ...(state.languages.length === 0 ? ['languages'] : []),
28
+ ...(state.defaultLanguage === undefined ? ['defaultLanguage'] : [])
29
+ ];
30
+ return {
31
+ languages: state.languages,
32
+ ...(state.defaultLanguage !== undefined ? { defaultLanguage: state.defaultLanguage } : {}),
33
+ missing,
34
+ runtimeResolvesTranslations: resolves,
35
+ note: resolves
36
+ ? 'Runtime çevirileri çözebilir; yazılı anahtarlar için translation_list.'
37
+ : `Runtime çevirileri ÇÖZEMEZ (kurulmamış: ${missing.join(', ')}): translation_update 200 dönse bile her etiket ekrana HAM ANAHTAR olarak basılır. Düzeltme: ${localizationFix(state)}.`
38
+ };
39
+ }
40
+ /**
41
+ * Platform null alanları serileştirmede düşürüyor: dili kurulmamış projenin yanıtında `languages`
42
+ * diye bir alan HİÇ YOK — ajan "boş" olduğunu bile göremiyor (canlı doğrulama 2026-09-09,
43
+ * `mcp-benchmark-2026-09-09`). Runtime ise dil kurulu değilken çeviriyi çözemeyip anahtarı basıyor,
44
+ * yani yokluk ekranı sessizce bozuyor (`S-15`). Blok yokluğu görünür kılar; platformun `data`'sı
45
+ * değiştirilmez, bilgi kardeş alan olarak eklenir.
46
+ */
47
+ function withLocalization(payload) {
48
+ const state = readProjectLanguages(payload);
49
+ if (!state || !payload || typeof payload !== 'object' || Array.isArray(payload))
50
+ return payload;
51
+ return { ...payload, localization: localizationBlock(state) };
52
+ }
5
53
  function pick(entry, keys) {
6
54
  if (!entry || typeof entry !== 'object')
7
55
  return {};
@@ -9,7 +57,15 @@ function pick(entry, keys) {
9
57
  .map(key => [key, entry[key]])
10
58
  .filter(([, value]) => value !== undefined && value !== null));
11
59
  }
60
+ /**
61
+ * `/datasources/tree` ucu `List<DatasourceTreeDto>`'yu `Ok(result)` ile ÇIPLAK DİZİ olarak yazar
62
+ * (DatasourcesController.cs:31-38); `ArrayBaseResponse` yalnız swagger notudur. Yalnız
63
+ * `payload.data`'ya bakan eski sürüm bu yüzden built-in datasource'u ve tüm entity'leri boş
64
+ * gösteriyordu (27/E-7) — overview'a güvenen ajan kurulu sistemin yarısını keşfedemiyordu.
65
+ */
12
66
  function rows(payload) {
67
+ if (Array.isArray(payload))
68
+ return payload;
13
69
  const data = payload?.data;
14
70
  return Array.isArray(data) ? data : [];
15
71
  }
@@ -18,7 +74,7 @@ const applicationInput = z.object({
18
74
  name: z.string().min(1),
19
75
  color: z.string().optional(),
20
76
  dns: z.string().optional(),
21
- type: z.string().optional().describe('ApplicationType; varsayılan Web')
77
+ type: z.enum(['web', 'mobile']).optional().describe("ApplicationType; geçerli değerler: 'web', 'mobile'; varsayılan 'web'")
22
78
  });
23
79
  const templateOptionsInput = z.object({
24
80
  templateId: guid,
@@ -34,29 +90,46 @@ export function registerProjectTools(server, platform) {
34
90
  }, handle(config, args => platform.org('GET', '/projects', { query: args })));
35
91
  server.registerTool('project_get', {
36
92
  title: 'Proje getir',
37
- description: 'Tek projeyi id ya da slug ile getirir; environment listesi de yanıttadır.',
93
+ description: "Tek projeyi id ya da slug ile getirir; environment listesi de yanıttadır. Yanıttaki 'localization' bloğu gateway'in eklediği türetilmiş alandır: platform dil alanlarını null olduklarında serileştirmede düşürüyor, blok bunu görünür kılar ve runtime'ın çevirileri çözüp çözemeyeceğini söyler.",
38
94
  inputSchema: { id: guid.optional(), slug: z.string().optional().describe('id verilmediyse zorunlu') },
39
95
  annotations: readOnly
40
- }, handle(config, args => {
41
- if (args.id)
42
- return platform.org('GET', `/projects/${args.id}`);
43
- if (args.slug)
44
- return platform.org('GET', `/p/${args.slug}`);
45
- throw new Error("'id' ya da 'slug' parametrelerinden biri zorunlu.");
96
+ }, handle(config, async (args) => {
97
+ if (!args.id && !args.slug) {
98
+ throw new Error("'id' ya da 'slug' parametrelerinden biri zorunlu.");
99
+ }
100
+ return withLocalization(await platform.org('GET', args.id ? `/projects/${args.id}` : `/p/${args.slug}`));
46
101
  }));
47
102
  server.registerTool('project_create', {
48
103
  title: 'Proje oluştur',
49
- description: "Yeni proje oluşturur. templateOptions verilmezse 'application' zorunludur (ilk application projeyle birlikte açılır). LoaderImage bu tool'da desteklenmez.",
104
+ description: "Yeni proje oluşturur. templateOptions verilmezse 'application' zorunludur (ilk application projeyle birlikte açılır). LoaderImage bu tool'da desteklenmez. " + constraintSummary('CreateOrganizationProjectCommand') + ` Gateway POST'tan önce project_list ile slug'ı arar; doluysa istek gitmez ve 409 anlamlı mesajla döner. DİL: create ucu artık languages/defaultLanguage alanlarını kendisi taşır ve gövdeyle birlikte gider. Vermezseniz gateway varsayılan [${SEED_LANGUAGES.join(', ')}] + '${SEED_DEFAULT_LANGUAGE}' tohumunu koyar ve bunu gatewayNormalized ile bildirir — dilsiz projede yazılan çeviriler runtime'da ham anahtar olarak çizilir (S-15). templateOptions verildiğinde TOHUM atlanır (template kendi dil ayarını taşır); açıkça verdiğiniz diller o yolda da gönderilir.`,
50
105
  inputSchema: {
51
106
  name: z.string().min(1),
52
- slug: z.string().min(1),
107
+ slug: z.string().min(1).describe('Organizasyonda tekil; veritabanı adı zeyrek_<org>_<slug> bundan türer ve sonradan değiştirmek bütün runtime rotalarını kırar'),
53
108
  theme: z.string().optional().describe('Tema JSON\'u string olarak'),
109
+ languages: z.array(z.string().min(1)).min(1).optional().describe(`Proje dilleri (ISO 639-1; 'tr-TR' gibi bölgeli değer kabul edilir ve 'tr'ye indirgenir, indirgeme gatewayNormalized ile bildirilir). Verilmezse gateway [${SEED_LANGUAGES.join(', ')}] tohumlar — projenin dilsiz doğmasına ve çevirilerin sessizce çözülmemesine izin verilmez`),
110
+ defaultLanguage: z.string().min(1).optional().describe(`languages içinde olmalı (indirgenmiş hâliyle karşılaştırılır); yalnız defaultLanguage verirseniz languages onunla kurulur. İkisi de yoksa '${SEED_DEFAULT_LANGUAGE}'`),
54
111
  application: applicationInput.optional(),
55
112
  templateOptions: templateOptionsInput.optional().describe('Verilirse proje template\'ten kurulur (asenkron); sonucu project_list ile izleyin')
56
113
  },
57
114
  annotations: writes
58
- }, handle(config, args => {
115
+ }, handle(config, async (args) => {
116
+ const seed = resolveSeedLanguages(args.languages, args.defaultLanguage);
117
+ const languages = normalizeLanguageCodes(seed.languages, 'languages');
118
+ const defaultLanguage = normalizeLanguageCode(seed.defaultLanguage, 'defaultLanguage');
119
+ if (!languages.codes.includes(defaultLanguage.code)) {
120
+ throw new PlatformApiError(`project_create: defaultLanguage '${defaultLanguage.code}' languages listesinde (${languages.codes.join(', ')}) yok — platform bunu 422 ile reddeder (PRJ-002). Kodu listeye ekleyin ya da listedeki bir kodu varsayılan yapın.`, 422);
121
+ }
122
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'create' }));
123
+ // PRJ-001: slug çakışması 2026-09-08 öncesi build'de 500 'DatabaseNameAlreadyExistsException'
124
+ // dönüyordu (3. koşu S-1); arama ucu düşerse ön kontrol atlanır, karar platforma kalır.
125
+ const existing = rows(await platform.org('GET', '/projects', { query: { searchTerm: args.slug, pageLength: 200 } }).catch(() => undefined))
126
+ .map(entry => entry)
127
+ .find(entry => entry.slug === args.slug);
128
+ if (existing) {
129
+ throw new PlatformApiError(`project_create: '${args.slug}' slug'ı bu organizasyonda zaten kullanımda (proje '${String(existing.name ?? '?')}', id ${String(existing.id ?? '?')}) — veritabanı adı zeyrek_<org>_<slug> tekildir, platform bunu reddeder. Başka bir slug seçin ya da mevcut projeyle devam edin (PRJ-001).`, 409, 'DatabaseNameAlreadyExistsException');
130
+ }
59
131
  const form = new URLSearchParams({ Name: args.name, Slug: args.slug });
132
+ const notes = [];
60
133
  if (args.theme)
61
134
  form.set('Theme', args.theme);
62
135
  if (args.application) {
@@ -75,30 +148,73 @@ export function registerProjectTools(server, platform) {
75
148
  form.set('TemplateOptions.TemplateReceiveUpdates', String(args.templateOptions.templateReceiveUpdates));
76
149
  }
77
150
  }
78
- return platform.org('POST', '/projects', { form });
151
+ // Dil artık create ucunun kendi alanı (CreateOrganizationProjectCommand.Languages /
152
+ // DefaultLanguage); ayrı bir PUT'a gerek yok. Template yolunda yalnız TOHUM atlanır: template
153
+ // kendi dil ayarını getiriyor ve uydurulmuş bir varsayılanla onu ezmek yanlış olur — çağıranın
154
+ // açıkça verdiği diller orada da gönderilir.
155
+ if (!seed.seeded || !args.templateOptions) {
156
+ languages.codes.forEach(code => form.append('Languages', code));
157
+ form.set('DefaultLanguage', defaultLanguage.code);
158
+ notes.push(...languages.notes);
159
+ if (defaultLanguage.note)
160
+ notes.push(defaultLanguage.note);
161
+ if (seed.seeded) {
162
+ notes.push(`Proje dilleri gateway tarafından varsayılanla kuruldu: languages [${languages.codes.join(', ')}], defaultLanguage '${defaultLanguage.code}'. Dilsiz projede yazılan çeviriler runtime'da ham anahtar olarak çizilir (S-15). Başka dil istiyorsanız project_update ile değiştirin.`);
163
+ }
164
+ }
165
+ return attachFindings(withNormalization(await platform.org('POST', '/projects', { form }), notes), findings);
79
166
  }));
80
167
  server.registerTool('project_update', {
81
168
  title: 'Proje güncelle',
82
- description: 'Proje adını, slug\'ını, temasını ve dil ayarlarını günceller. languages/defaultLanguage verilmezse mevcut değerlerine dokunulmaz. LoaderImage bu tool\'da desteklenmez.',
169
+ description: "Proje adını, slug'ını, temasını ve dil ayarlarını günceller. languages/defaultLanguage verilmezse mevcut değerlerine dokunulmaz. SLUG: vermezseniz gateway projeyi okuyup MEVCUT slug'ı gövdeye kendisi koyar ve bunu gatewayNormalized ile bildirir — platform slug'ı ADDAN yeniden üretiyor ve slug runtime rotasının parçası olduğu için (/{projectSlug}/{application}/{page}) tek çağrı bütün sayfa URL'lerini ve DEFAULT_PROJECT çözümlemesini kırıyordu (ölçüldü 2026-09-05). Slug'ı gerçekten değiştirmek istiyorsanız açıkça yazın; yanıt o zaman rotanın da değiştiğini söyler. LoaderImage bu tool'da desteklenmez.",
83
170
  inputSchema: {
84
171
  id: guid,
85
- name: z.string().min(1),
172
+ name: z.string().min(1).optional().describe('Verilmezse mevcut ad korunur — yalnız dil ayarını değiştirmek için adı tekrar göndermeniz gerekmez'),
86
173
  slug: z.string().optional(),
87
174
  theme: z.string().optional(),
88
- languages: z.array(z.string().min(1)).min(1).optional().describe('ISO 639-1 kod listesi (proje dilleri); sayfa çevirileri bu kodlarla doğrulanır'),
89
- defaultLanguage: z.string().min(1).optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir")
175
+ languages: z.array(languageCode).min(1).optional().describe("ISO 639-1 iki harfli kod listesi (proje dilleri, platformun 184 kodluk sözlüğünden — PRJ-002). Runtime çevirileri YALNIZ bu kodlarla çözer; liste kurulmadan yazılan çeviriler 200 döner ama ekrana ham anahtar basılır (S-15). Yeni proje bu liste BOŞ doğar — çeviri kullanacaksanız burayı kurmak ilk adımdır"),
176
+ defaultLanguage: languageCode.optional().describe("languages içinde olmalı — proje dilleri hiç kurulmadıysa (ilk kurulumda) tek başına verilebilir. Runtime kayıtlı dil seçimi yokken buna düşer; boşsa aktif dil hiç seçilmez ve ilk açılışta bütün etiketler ham anahtar çizilir")
90
177
  },
91
178
  annotations: updates
92
- }, handle(config, args => {
93
- const form = new URLSearchParams({ Name: args.name });
94
- if (args.slug)
95
- form.set('Slug', args.slug);
179
+ }, handle(config, async (args) => {
180
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'project', args, { operation: 'update' }));
181
+ // Platform gövdede slug yoksa onu ADDAN yeniden üretiyor. Yalnız dil ayarını değiştiren bir
182
+ // çağrı bu yüzden 'test_dev' slug'ını 'mcp-benchmark' yapmıştı ve o an bütün runtime URL'leri
183
+ // kırılmıştı (09-05 Corecruiter koşusu, S-11). Gateway mevcut değerleri okuyup gövdeye koyar;
184
+ // çağıran açıkça yazdıysa dokunmaz ama rotanın değiştiğini yüksek sesle söyler.
185
+ const current = ((await platform.org('GET', `/projects/${args.id}`))?.data ?? {});
186
+ const currentSlug = typeof current.slug === 'string' ? current.slug : undefined;
187
+ const currentName = typeof current.name === 'string' ? current.name : undefined;
188
+ const name = args.name ?? currentName;
189
+ const slug = args.slug ?? currentSlug;
190
+ if (name === undefined) {
191
+ throw new PlatformApiError(`'${args.id}' projesinin adı okunamadı; name alanını açıkça verin (platform bu uçta adı zorunlu tutuyor).`, 400);
192
+ }
193
+ const form = new URLSearchParams({ Name: name });
194
+ if (slug)
195
+ form.set('Slug', slug);
96
196
  if (args.theme)
97
197
  form.set('Theme', args.theme);
98
- args.languages?.forEach(language => form.append('Languages', language));
99
- if (args.defaultLanguage)
100
- form.set('DefaultLanguage', args.defaultLanguage);
101
- return platform.org('PUT', `/projects/${args.id}`, { form });
198
+ const languages = args.languages ? normalizeLanguageCodes(args.languages, 'languages') : undefined;
199
+ const defaultLanguage = args.defaultLanguage ? normalizeLanguageCode(args.defaultLanguage, 'defaultLanguage') : undefined;
200
+ languages?.codes.forEach(code => form.append('Languages', code));
201
+ if (defaultLanguage)
202
+ form.set('DefaultLanguage', defaultLanguage.code);
203
+ const payload = await platform.org('PUT', `/projects/${args.id}`, { form });
204
+ const notes = [
205
+ ...(languages?.notes ?? []),
206
+ ...(defaultLanguage?.note ? [defaultLanguage.note] : []),
207
+ ...(args.slug === undefined && currentSlug !== undefined
208
+ ? [`project_update gövdesine mevcut slug ('${currentSlug}') gateway tarafından kondu: platform slug'ı vermediğinizde ADDAN yeniden üretiyor ve slug runtime rotasının parçası. Slug'ı değiştirmek istiyorsanız açıkça yazın.`]
209
+ : []),
210
+ ...(args.slug !== undefined && currentSlug !== undefined && args.slug !== currentSlug
211
+ ? [`DİKKAT: proje slug'ı '${currentSlug}' → '${args.slug}' değişti; runtime rotası da değişti (/${args.slug}/{application}/{page}). Kurulu sayfa URL'leri ve gateway'in DEFAULT_PROJECT ayarı eski slug'a bakıyorsa artık çözülmez.`]
212
+ : []),
213
+ ...(args.name === undefined && currentName !== undefined
214
+ ? [`project_update gövdesine mevcut ad ('${currentName}') gateway tarafından kondu (name verilmedi).`]
215
+ : [])
216
+ ];
217
+ return attachFindings(withNormalization(payload, notes), findings);
102
218
  }));
103
219
  server.registerTool('project_delete', {
104
220
  title: 'Proje sil',
@@ -108,7 +224,7 @@ export function registerProjectTools(server, platform) {
108
224
  }, handle(config, args => platform.org('DELETE', `/projects/${args.id}`)));
109
225
  server.registerTool('project_overview', {
110
226
  title: 'Proje haritası',
111
- description: 'Projenin kompakt envanteri: application\'lar, datasource+entity ağacı, microservice/workflow/query/SAYFA listeleri (yalnız id/ad/tip) + tutarlılık boşlukları (coverageGaps: kimlik var ama giriş sayfası yok, yazma ucu var ama sayfa yok). Var olan bir projede çalışmaya HER ZAMAN buradan başlayın; detay için ilgili *_get tool\'unu kullanın.',
227
+ description: 'Projenin kompakt envanteri: application\'lar (microservice/workflow/sayfa/layout listeleri + coverageGaps), datasource→entity ağacı (built-in dâhil), query/view/dto listeleri — hepsi yalnız id/ad/tip. Var olan bir projede çalışmaya HER ZAMAN buradan başlayın; detay için ilgili *_get tool\'unu, automation için automation_list(entityId)\'yi kullanın.',
112
228
  inputSchema: { project: projectParam, environment: environmentParam },
113
229
  annotations: readOnly
114
230
  }, handle(config, async ({ project, environment }) => {
@@ -118,40 +234,74 @@ export function registerProjectTools(server, platform) {
118
234
  const applicationsPayload = projectData.id
119
235
  ? await platform.org('GET', '/applications', { query: { projectId: projectData.id, pageLength: 100 } })
120
236
  : { data: [] };
121
- const datasourceTreePayload = await platform.tenant('GET', projectSlug, '/datasources/tree', { environment });
237
+ // `/datasources/tree` ucunun `DatasourceTreeDto`'su `IsBuiltIn` alanını HİÇ taşımıyor; bayrak
238
+ // yalnız `DatasourceDto`'da (`/datasources`) var. Ajan var olan projede her zaman buradan
239
+ // başladığı için "hangisi projenin kendi veritabanı" sorusu haritadan yanıtlanamıyordu ve
240
+ // 09-06·2 koşusunda ajan built-in'in parolasını arayıp beş çağrı harcadı (B-59). Bayrak
241
+ // listeden birleştirilir; sabit built-in GUID'ine BAKILMAZ — o bir seed detayıdır.
242
+ const [datasourceTreePayload, datasourceListPayload] = await Promise.all([
243
+ platform.tenant('GET', projectSlug, '/datasources/tree', { environment }),
244
+ platform.tenant('GET', projectSlug, '/datasources', { environment, query: { pageLength: 200 } }).catch(() => undefined)
245
+ ]);
246
+ const datasourceFlags = new Map(rows(datasourceListPayload)
247
+ .map(entry => [String(entry.id ?? ''), entry.isBuiltIn === true]));
248
+ // Uç düşerse bayrak UYDURULMAZ: alan haritadan tamamen çıkar ve hint bunu söyler. Hepsine
249
+ // `false` yazmak sessiz bir yalan olurdu.
250
+ const flagsAvailable = datasourceListPayload !== undefined;
122
251
  const queriesPayload = await platform.tenant('GET', projectSlug, '/queries', { environment, query: { pageLength: 200 } });
252
+ const [viewsPayload, dtosPayload] = await Promise.all([
253
+ platform.tenant('GET', projectSlug, '/views', { environment, query: { pageLength: 200 } }).catch(() => undefined),
254
+ platform.tenant('GET', projectSlug, '/datatransferobjects', { environment, query: { pageLength: 200 } }).catch(() => undefined)
255
+ ]);
123
256
  const applications = [];
124
257
  for (const applicationEntry of rows(applicationsPayload)) {
125
258
  const applicationId = applicationEntry.id;
126
- const [microservicesPayload, workflowsPayload, pagesPayload] = await Promise.all([
259
+ const [microservicesPayload, workflowsPayload, pagesPayload, layoutsPayload] = await Promise.all([
127
260
  platform.tenant('GET', projectSlug, `/microservices/${applicationId}`, { environment, query: { pageLength: 200 } }).catch(() => undefined),
128
261
  platform.tenant('GET', projectSlug, `/workflows/${applicationId}`, { environment, query: { pageLength: 200 } }).catch(() => undefined),
129
- platform.tenant('GET', projectSlug, `/pages/${applicationId}`, { environment, query: { pageIndex: 0, pageLength: 200 } }).catch(() => undefined)
262
+ platform.tenant('GET', projectSlug, `/pages/${applicationId}`, { environment, query: { pageIndex: 0, pageLength: 200 } }).catch(() => undefined),
263
+ platform.tenant('GET', projectSlug, `/layouts/${applicationId}`, { environment, query: { pageLength: 200 } }).catch(() => undefined)
130
264
  ]);
131
265
  const microservices = rows(microservicesPayload).map(entry => pick(entry, ['id', 'slug', 'name', 'type', 'httpMethod', 'source', 'entityId', 'workflowId']));
132
266
  const workflows = rows(workflowsPayload).map(entry => pick(entry, ['id', 'slug', 'name', 'type', 'entityId']));
133
267
  const pages = rows(pagesPayload).map(entry => pick(entry, ['id', 'slug', 'name']));
134
- const gaps = projectCoverageGaps({ pages, microservices, workflows });
268
+ const layouts = rows(layoutsPayload).map(entry => pick(entry, ['id', 'slug', 'name']));
135
269
  applications.push({
136
270
  ...pick(applicationEntry, ['id', 'slug', 'name', 'type']),
137
271
  microservices,
138
272
  workflows,
139
273
  pages,
140
- ...(gaps.length > 0 ? { coverageGaps: gaps } : {})
274
+ layouts,
275
+ coverageGaps: projectCoverageGaps({ pages, microservices, workflows })
141
276
  });
142
277
  }
278
+ // Dil durumu haritanın parçası: `pick` null/undefined alanları eler, yani dilsiz projede
279
+ // languages haritadan da sessizce düşerdi — eksikliği görünmez kılan tam o davranış (`S-15`).
280
+ const languageState = readProjectLanguages(projectPayload);
143
281
  return {
144
- project: pick(projectData, ['id', 'slug', 'name']),
282
+ project: pick(projectData, ['id', 'slug', 'name', 'languages', 'defaultLanguage']),
283
+ ...(languageState ? { localization: localizationBlock(languageState) } : {}),
145
284
  environments: (Array.isArray(projectData.environments) ? projectData.environments : []).map(entry => pick(entry, ['id', 'name'])),
146
285
  environment: platform.resolveEnvironment(environment),
147
286
  applications,
148
287
  datasources: rows(datasourceTreePayload).map(datasourceEntry => ({
149
288
  ...pick(datasourceEntry, ['id', 'name', 'type']),
289
+ ...(flagsAvailable ? { isBuiltIn: datasourceFlags.get(String(datasourceEntry.id ?? '')) ?? false } : {}),
150
290
  entities: (Array.isArray(datasourceEntry.entities) ? datasourceEntry.entities : [])
151
- .map(entry => pick(entry, ['id', 'slug', 'name']))
291
+ .map(entry => pick(entry, ['id', 'slug', 'name', 'automationCount']))
152
292
  })),
153
293
  queries: rows(queriesPayload).map(entry => pick(entry, ['id', 'name', 'datasourceId'])),
154
- hint: 'Detay: entity_get/microservice_get/workflow_get (büyük kayıtlarda view:\'outline\'); değişiklik: *_patch.'
294
+ views: rows(viewsPayload).map(entry => pick(entry, ['id', 'name'])),
295
+ dtos: rows(dtosPayload).map(entry => pick(entry, ['id', 'name'])),
296
+ hint: [
297
+ flagsAvailable
298
+ ? "Yeni entity'ler isBuiltIn: true olan datasource'a açılır (projenin kendi veritabanı); dış bir DB bağlamak yalnız kullanıcı kendi veritabanını verdiyse gerekir."
299
+ : "DİKKAT: built-in bayrağı okunamadı (/datasources ucu yanıt vermedi), bu haritada isBuiltIn alanı YOK — datasource_list ile bakın.",
300
+ ...(languageState && !runtimeResolvesTranslations(languageState)
301
+ ? [`DİKKAT: projenin dil ayarı eksik (localization bloğuna bakın) — çeviri yazsanız bile runtime her etiketi ham anahtar olarak basar. Etiketleri translation_* üzerinden verecekseniz ÖNCE ${localizationFix(languageState)}.`]
302
+ : []),
303
+ "Detay: entity_get/microservice_get/workflow_get (büyük kayıtlarda view:'outline'); değişiklik: *_patch. Automation'lar entity başına: automation_list(entityId) — ağaçtaki automationCount yalnız platform sayıyorsa görünür."
304
+ ].join(' ')
155
305
  };
156
306
  }));
157
307
  }
@@ -1,6 +1,15 @@
1
- import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
1
+ import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive, languageParam } from './common.js';
2
+ import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
3
+ import { languageHeader } from '../knowledge/languageCodes.js';
4
+ import { withNormalization } from '../knowledge/normalize.js';
5
+ import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
2
6
  import { handle } from '../platform/responses.js';
3
7
  import { z } from 'zod';
8
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
9
+ import { policyRules } from '../knowledge/rules.js';
10
+ import { constraintSummary } from '../knowledge/backendConstraints.js';
11
+ const policyOverrideParam = z.boolean().optional()
12
+ .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
4
13
  export function registerQueryTools(server, platform) {
5
14
  const config = platform.config;
6
15
  server.registerTool('query_list', {
@@ -17,16 +26,24 @@ export function registerQueryTools(server, platform) {
17
26
  }, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/queries/${id}`, { environment })));
18
27
  server.registerTool('query_create', {
19
28
  title: 'Query oluştur',
20
- description: 'Yeni query tanımlar. definition = CreateQueryCommand gövdesi.',
21
- inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
29
+ description: "Yeni query tanımlar (ham SQL). definition = CreateQueryCommand gövdesi. AGREGASYON VE RAPORLAMANIN YOLU BUDUR (SUM/COUNT/AVG/GROUP BY, dönemsel özet, KPI) — view_* bir TAM METİN ARAMA indeksidir, özet üretmez (09-02 M-3). VAR OLAN SATIRLARDAN TÜRETİLEN SATIR YAZMANIN DA YOLU BUDUR: 'INSERT … SELECT' kaydedip query kaynaklı bir uçtan çalıştırmak, geçmiş/hareket/junction tablosunu TEK çağrıda doldurur ve hiçbir id elle taşınmaz (ölçüldü 2026-09-07; reçete platform_guide('entity') §'Toplu ve türetilmiş satır'). Kayıt sırasında gövde bir kez ÇALIŞTIRILIR ama geri alınır — yazan sorguyu kaydetmek satır bırakmaz ve 'returnScheme' boş döner. Kayıtlı query'nin parametreleri parameterScheme ile tanımlanır ve runtime'da bağlanır; tüketimi source:'query' microservice'idir. PARAMETRENİN DEĞERİ HER ZAMAN ÇAĞRIDAN GELİR (body → query-string → header): parametreye ifade kaynağı verilemez (parameters[].source komut şemasında yoktur, platform sessizce atar) ve middleware'in kurduğu user bu yola girmez. Kimlik/tenant kapsamı sunucu tarafında zorlanacaksa sorguyu bir workflow query adımından çağırın — reçete platform_guide('microservice') §'Kimlik kapsaması × uç kaynağı'. BOŞ STRING parametre NULL'a çevrilir: '= @p' koşulu hiçbir satırla eşleşmez ve sorgu sessizce 0 satır döner — 'filtre yok' için (@p IS NULL OR ...) yazın ya da sentinel kullanın. Yorum içindeki belirteç parametre sayılmaz. SQL çalıştırılamazsa kayıt YİNE de oluşur: returnScheme boş kalır ve yanıttaki message nedeni söyler — SQL'i düzeltip query_update ile kaydedin. SORGUNUN KENDİ AÇIKLAMASI VAR: `description` alanını doldurun — sorgu listesinde ad ve slug dışında ipucu yoktur ve ucun açıklaması (MS-014) bunun yerine geçmez, çünkü bir sorgu birden çok uçtan tüketilir; boş bırakılırsa gateway `QRY-006` uyarısı düşürür (yazmayı durdurmaz). " + constraintSummary('CreateQueryCommand'),
30
+ inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam, policyOverride: policyOverrideParam },
22
31
  annotations: writes
23
- }, handle(config, ({ project, environment, definition }) => platform.tenant('POST', project, '/queries', { environment, body: definition })));
32
+ }, handle(config, async ({ project, environment, definition, policyOverride }) => {
33
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'query', definition, { operation: 'create' }, policyOverride));
34
+ const payload = await platform.tenant('POST', project, '/queries', { environment, body: definition });
35
+ return attachFindings(withUnmappedMembers(withWriteDiff(payload, writeDiff(definition, payload, { parameters: 'parameterScheme' })), unmappedMembers('CreateQueryCommand', definition)), findings);
36
+ }));
24
37
  server.registerTool('query_update', {
25
38
  title: 'Query güncelle',
26
- description: 'Query tanımını günceller. definition = UpdateQueryCommand gövdesi.',
27
- inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam },
39
+ description: "Query tanımını günceller. definition = UpdateQueryCommand gövdesi. " + "SORGUNUN KENDİ AÇIKLAMASI VAR: `description` alanını doldurun — sorgu listesinde ad ve slug dışında ipucu yoktur ve ucun açıklaması (MS-014) bunun yerine geçmez, çünkü bir sorgu birden çok uçtan tüketilir; boş bırakılırsa gateway `QRY-006` uyarısı düşürür (yazmayı durdurmaz). " + constraintSummary('UpdateQueryCommand'),
40
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
28
41
  annotations: updates
29
- }, handle(config, ({ project, environment, id, definition }) => platform.tenant('PUT', project, `/queries/${id}`, { environment, body: definition })));
42
+ }, handle(config, async ({ project, environment, id, definition, policyOverride }) => {
43
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'query', definition, { operation: 'update' }, policyOverride));
44
+ const payload = await platform.tenant('PUT', project, `/queries/${id}`, { environment, body: definition });
45
+ return attachFindings(withUnmappedMembers(withWriteDiff(payload, writeDiff(definition, payload, { parameters: 'parameterScheme' })), unmappedMembers('UpdateQueryCommand', definition)), findings);
46
+ }));
30
47
  server.registerTool('query_delete', {
31
48
  title: 'Query sil',
32
49
  description: 'Query tanımını siler.',
@@ -41,8 +58,13 @@ export function registerQueryTools(server, platform) {
41
58
  }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/queries/${id}/clone`, { environment })));
42
59
  server.registerTool('query_preview', {
43
60
  title: 'Query önizle',
44
- description: 'Kaydedilmemiş query tanımını datasource üzerinde çalıştırır. definition = PreviewQueryCommand gövdesi.',
45
- inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, definition: definitionParam },
61
+ description: "Kaydedilmemiş query tanımını datasource üzerinde çalıştırır. definition = PreviewQueryCommand gövdesi. Preview ROLLBACK edilir: yazan bir sorgu (INSERT/UPDATE/DELETE) çalışır, affectedRowCount gerçek sayıyı döner ama SATIR KALMAZ (ölçüldü, 09-04 Y-19) — yazmayı doğrulamak için sorguyu kaydedip microservice/workflow üzerinden çalıştırın. YANITTA ROLLBACK İŞARETİ YOKTUR: 2026-09-07'de ölçüldü, yazan sorgunun yanıtı yalnız { affectedRowCount, elapsedMilliseconds } taşıyor — sayı başarı gibi görünür, tek kanıt sonradan row_list çekmektir. BOŞ STRING parametre NULL'a çevrilir (üç koşuda ölçüldü): '= @p' hiçbir satırla eşleşmez, 0 satır döner ve hata yoktur. Davranış kuralları: platform_guide('microservice'). " + constraintSummary('PreviewQueryCommand'),
62
+ inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid, definition: definitionParam, language: languageParam, policyOverride: policyOverrideParam },
46
63
  annotations: writes
47
- }, handle(config, ({ project, environment, datasourceId, definition }) => platform.tenant('POST', project, `/queries/${datasourceId}/preview`, { environment, body: definition })));
64
+ }, handle(config, async ({ project, environment, datasourceId, definition, language, policyOverride }) => {
65
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'query', definition, { operation: 'preview' }, policyOverride));
66
+ const notes = [];
67
+ const headers = languageHeader(language, notes);
68
+ return attachFindings(withNormalization(await platform.tenant('POST', project, `/queries/${datasourceId}/preview`, { environment, body: definition, headers }), notes), findings);
69
+ }));
48
70
  }