@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.
- package/.env.example +8 -3
- package/.env.production.example +2 -0
- package/README.md +229 -193
- package/dist/config.js +20 -2
- package/dist/knowledge/backendConstraints.js +897 -0
- package/dist/knowledge/componentCatalog.js +196 -42
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/guideSections.js +49 -0
- package/dist/knowledge/hydrate.js +97 -0
- package/dist/knowledge/languageCodes.js +36 -0
- package/dist/knowledge/languages.js +105 -0
- package/dist/knowledge/layoutSummary.js +10 -0
- package/dist/knowledge/localization.js +51 -0
- package/dist/knowledge/normalize.js +449 -16
- package/dist/knowledge/outlines.js +136 -12
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +618 -43
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/errors.js +46 -4
- package/dist/platform/http.js +33 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +9 -2
- package/dist/platform/screenshot.js +527 -16
- package/dist/platform/secrets.js +40 -0
- package/dist/platform/tokenVault.js +89 -0
- package/dist/platform/writeDiff.js +281 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +2 -2
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +3 -2
- package/dist/resources/guides/component.md +69 -8
- package/dist/resources/guides/entity.md +404 -16
- package/dist/resources/guides/expressions.md +44 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/getting-started.md +77 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/microservice.md +226 -13
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/page.md +383 -46
- package/dist/resources/guides/translation.md +126 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +198 -27
- package/dist/resources/knowledge.js +5 -0
- package/dist/resources/rules.js +27 -0
- package/dist/resources/schema-fingerprint.json +8 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
- package/dist/resources/schemas/CreateEntityCommand.json +52 -12
- package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
- package/dist/resources/schemas/CreateQueryCommand.json +56 -3
- package/dist/resources/schemas/CreateRowCommand.json +38 -0
- package/dist/resources/schemas/CreateViewCommand.json +13 -10
- package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
- package/dist/resources/schemas/GetRowsQuery.json +3 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
- package/dist/resources/schemas/StepDto.json +23 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
- package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
- package/dist/resources/schemas/UpdateRowCommand.json +40 -0
- package/dist/resources/schemas/UpdateViewCommand.json +15 -10
- package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
- package/dist/server.js +4 -0
- package/dist/tools/access.js +3 -3
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +63 -30
- package/dist/tools/common.js +39 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +20 -6
- package/dist/tools/flows.js +110 -11
- package/dist/tools/folders.js +7 -2
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/microservices.js +229 -38
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +365 -100
- package/dist/tools/platformInfo.js +152 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/rows.js +159 -45
- package/dist/tools/screenWrite.js +59 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +33 -16
- 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:
|
|
102
|
+
description: guideDescription,
|
|
17
103
|
inputSchema: {
|
|
18
|
-
guide:
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
41
|
-
|
|
42
|
-
|
|
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
|
}
|
package/dist/tools/projects.js
CHANGED
|
@@ -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.
|
|
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:
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
89
|
-
defaultLanguage:
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/tools/queries.js
CHANGED
|
@@ -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:
|
|
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 }) =>
|
|
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:
|
|
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 }) =>
|
|
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:
|
|
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 }) =>
|
|
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
|
}
|