@appaflytech/zeyrek-ai-mcp 0.0.20 → 0.0.22
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/README.md +14 -13
- package/dist/knowledge/componentCatalog.js +937 -212
- package/dist/knowledge/componentEvents.js +366 -66
- package/dist/knowledge/mobileComponentCatalog.js +213 -1
- package/dist/knowledge/mobileComponentEvents.js +48 -0
- package/dist/knowledge/normalize.js +55 -11
- package/dist/knowledge/platform/mobileRules.js +7 -5
- package/dist/knowledge/rules.js +21 -0
- package/dist/platform/diagnostics.js +85 -0
- package/dist/platform/ifExists.js +129 -0
- package/dist/platform/responses.js +2 -1
- package/dist/platform/strictArguments.js +22 -2
- package/dist/platform/writeDiff.js +58 -31
- package/dist/platform/writeView.js +17 -0
- package/dist/resources/guides/component.md +9 -1
- package/dist/resources/guides/functions.md +31 -2
- package/dist/resources/guides/getting-started.md +35 -0
- package/dist/resources/guides/mobile.md +51 -2
- package/dist/resources/guides/page.md +34 -16
- package/dist/resources/guides/translation.md +5 -6
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +18 -0
- package/dist/tools/designSchemaPatch.js +9 -5
- package/dist/tools/flows.js +36 -42
- package/dist/tools/functions.js +20 -10
- package/dist/tools/microservices.js +39 -11
- package/dist/tools/mobile.js +1 -1
- package/dist/tools/pages.js +65 -17
- package/dist/tools/schemaOps.js +2 -2
- package/dist/tools/workflows.js +39 -12
- package/package.json +7 -2
package/dist/tools/pages.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps, isBranchName } from './common.js';
|
|
2
|
-
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, lostSetterTargets, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
|
|
2
|
+
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, lostSetterTargets, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
|
|
3
3
|
import { functionSignature } from '../knowledge/functionGuards.js';
|
|
4
4
|
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath } from './screens.js';
|
|
5
5
|
import { fetchComponentEventNames } from './components.js';
|
|
6
6
|
import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
|
|
7
7
|
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, planEventRemovals, resizeServerOps, planResizes, planPropTypes, rejectedOpsMessage } from './schemaOps.js';
|
|
8
8
|
import { formBindingWarnings, formSubmitButtonWarnings } from '../knowledge/coverage.js';
|
|
9
|
-
import {
|
|
9
|
+
import { emptyWriteDiff, schemaOpDrops, unwrapProps, withWriteDiff, writeDiff } from '../platform/writeDiff.js';
|
|
10
10
|
import { PlatformApiError } from '../platform/errors.js';
|
|
11
|
+
import { existingMatch, ifExistsParam, listAll, withOutcome } from '../platform/ifExists.js';
|
|
11
12
|
import { handle, toolResult, toolError } from '../platform/responses.js';
|
|
12
13
|
import { StudioScreenshotter } from '../platform/capture/index.js';
|
|
13
14
|
import { captureTargetFor } from '../platform/capture/target.js';
|
|
@@ -59,6 +60,10 @@ async function orderByIndex(ops, readPage) {
|
|
|
59
60
|
return { ops, moved: [], warnings: [] };
|
|
60
61
|
}
|
|
61
62
|
}
|
|
63
|
+
/** Şema ağacındaki TÜM düğümler (kök + iç içe çocuklar); page_get ve page_create özeti aynı sayımı kullanır. */
|
|
64
|
+
function countSchemaNodes(nodes) {
|
|
65
|
+
return (nodes ?? []).reduce((total, node) => total + 1 + countSchemaNodes(node.children), 0);
|
|
66
|
+
}
|
|
62
67
|
async function fetchPage(platform, project, environment, applicationId, pageId) {
|
|
63
68
|
const response = await platform.tenant('GET', project, `/pages/${applicationId}/${pageId}`, { environment });
|
|
64
69
|
return response?.data ?? {};
|
|
@@ -71,7 +76,7 @@ async function fetchPage(platform, project, environment, applicationId, pageId)
|
|
|
71
76
|
*/
|
|
72
77
|
export const SERVER_PAGE_OPS = [
|
|
73
78
|
'replace_prop', 'set_binding', 'set_prop_type', 'add_component', 'remove_component', 'move_component', 'reorder', 'set_block',
|
|
74
|
-
'set_appearance', 'set_states', 'set_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
|
|
79
|
+
'set_appearance', 'set_states', 'set_event', 'add_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
|
|
75
80
|
];
|
|
76
81
|
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory) {
|
|
77
82
|
const config = platform.config;
|
|
@@ -106,7 +111,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
106
111
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
107
112
|
const resolved = await resolveProfile(platform, applicationId);
|
|
108
113
|
const layout = (page.layout ?? {});
|
|
109
|
-
const countNodes = (nodes) => (nodes ?? []).reduce((total, node) => total + 1 + countNodes(node.children), 0);
|
|
110
114
|
return {
|
|
111
115
|
id: page.id,
|
|
112
116
|
name: page.name,
|
|
@@ -123,7 +127,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
123
127
|
events: page.events ?? [],
|
|
124
128
|
flows: (Array.isArray(page.flows) ? page.flows : [])
|
|
125
129
|
.map(flow => ({ id: flow.id, name: flow.name, slug: flow.slug })),
|
|
126
|
-
schemaNodes:
|
|
130
|
+
schemaNodes: countSchemaNodes(page.schema),
|
|
127
131
|
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt,
|
|
128
132
|
...applicationTypeField(resolved)
|
|
129
133
|
};
|
|
@@ -214,7 +218,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
214
218
|
const maxInlinePngBytes = 5 * 1024 * 1024;
|
|
215
219
|
server.registerTool('page_screenshot', {
|
|
216
220
|
title: 'Sayfanın ekran görüntüsünü al',
|
|
217
|
-
description: "Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama).",
|
|
221
|
+
description: "ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama).",
|
|
218
222
|
inputSchema: {
|
|
219
223
|
project: projectParam,
|
|
220
224
|
environment: environmentParam,
|
|
@@ -247,7 +251,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
247
251
|
value: z.unknown().optional().describe("'set' için yazılacak değer. Tarih kutularına ISO yazın ('2026-08-01'); çok alanlı tarih aralığında kök DEĞİL kutu hedeflenir: '<elementId>__start' / '<elementId>__end'. 'tab' için sekmenin ANAHTARI ('iot') ya da görünen metni; 'select' için seçeneğin value'su ya da görünen metni"),
|
|
248
252
|
ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi")
|
|
249
253
|
})).max(20).optional().describe("Kare ALINMADAN ÖNCE sayfada koşturulacak etkileşim adımları: [{action:'set', target:'flt_month', value:'2022-10'}, {action:'click', target:'btn_sign_in'}, {action:'tab', target:'tabs_settings', value:'iot'}, {action:'select', target:'dd_city', value:'34'}, {action:'submit', target:'frm_note'}, {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. 'tab' SEKME SEÇER: sekme grubunun köküne 'click' göndermek DOM sırasındaki İLK sekmeye gider ve ikinci sekme hiç açılamıyordu — tablist köküne click artık reddedilir ve bu fiile yönlendirir. Eşleme önce '<elementId>__tab-<sekme anahtarı>' kimliğiyle, o yoksa görünen metinle yapılır; actionsReport.matchedBy hangisi olduğunu, actionsReport.activeTab adımdan SONRA hangi sekmenin açık olduğunu söyler. Etkin olmayan sekmenin içeriği DOM'da YOKTUR: o sekmedeki bir düğüme yazmadan önce 'tab' adımı gerekir. 'select' açılır kutudan seçer — kutu native alan değildir, değeri yazmak hiçbir şey yapmaz, seçeneğe tıklanması gerekir; liste 3 sn'ye kadar YOKLANIR (sorgudan beslenen kutuda seçenekler geç basılır) ve actionsReport.optionsSeen + waitedMs neyin görüldüğünü söyler. 'submit' formu gönder butonu OLMADAN gönderir (form.requestSubmit); actionsReport.submitVia hangi yolun kullanıldığını yazar. 'drag' HTML5 sürükle-bırak olaylarını (dragstart→dragover→drop→dragend) hedefin merkezinde atar; kanban cardMove ve takvim eventMove bununla tetiklenir. 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez); ardından kök düğümün runtime prop'larını yeniden okur: actionsReport.propWritten değerin prop'a ULAŞIP ulaşmadığıdır ve false ise applied da false döner — DOM'a yazılmış ama bileşenin duymadığı değer 'uygulandı' sayılmaz (G-14). Her adımdan sonra yerleşme beklenir ve yanıttaki actionsReport hedefin bulunup bulunmadığını ve adımdan sonra KAÇ AĞ İSTEĞİ çıktığını söyler — 'bağ kuruldu' ile 'çalışıyor' arasındaki farkı ancak bu ayırır (G-24)."),
|
|
250
|
-
language: z.string().min(1).optional().describe("Kareyi BU DİLDE çeker: adrese '?lang=<kod>' eklenir ve runtime dili oradan seçer (öncelik: ?lang → tarayıcıda kayıtlı seçim → tarayıcı dilleri → projenin varsayılanı). Seçim hem çeviriye bağlı prop'ları hem çok dilli alanların satır değerlerini kapsar, yani ikinci dilin karesi için sayfaya dil değiştirici KOYMAK GEREKMEZ — referans tasarımı bozulmadan ikinci dil kanıtlanır. ISO 639-1 iki harfli kod ('tr', 'en'); bölgeli kod ('tr-TR') indirgenir ve indirgeme yanıtta bildirilir. Kod projenin languages listesinde yoksa çağrı REDDEDİLİR: runtime tanımadığı kodu yok sayıp kareyi varsayılan dilde çizerdi ve 'çevrildi' sanılan yanlış bir kare raporlanırdı."),
|
|
254
|
+
language: z.string().min(1).optional().describe("Kareyi BU DİLDE çeker: adrese '?lang=<kod>' eklenir ve runtime dili oradan seçer (öncelik: ?lang → tarayıcıda kayıtlı seçim → tarayıcı dilleri → projenin varsayılanı). Seçim hem çeviriye bağlı prop'ları hem çok dilli alanların satır değerlerini kapsar, yani ikinci dilin karesi için sayfaya dil değiştirici KOYMAK GEREKMEZ — referans tasarımı bozulmadan ikinci dil kanıtlanır. ISO 639-1 iki harfli kod ('tr', 'en'); bölgeli kod ('tr-TR') indirgenir ve indirgeme yanıtta bildirilir. Kod projenin languages listesinde yoksa çağrı REDDEDİLİR: runtime tanımadığı kodu yok sayıp kareyi varsayılan dilde çizerdi ve 'çevrildi' sanılan yanlış bir kare raporlanırdı. MOBİL uygulamada da geçerlidir (/preview aynı ?lang'i okur); cihazın kendisinde ?lang yoktur (platform_guide('mobile') §Çok dil)."),
|
|
251
255
|
colorScheme: z.enum(['light', 'dark']).optional().describe("Kareyi açık ya da koyu temada çeker. Ziyaretçi tema tercihi çerezde değil localStorage'da yaşar ve tema store'u onu sayfa açılırken bir kez okur; gateway tercihi sayfa script'lerinden ÖNCE eker, bu yüzden sayfada 'theme-toggle' komponenti OLMASA DA koyu tema karesi alınabilir. Verilmezse projenin kendi modu çizilir."),
|
|
252
256
|
format: z.enum(['png', 'jpeg']).optional().describe("Kare biçimi; varsayılan 'png'. Doğrulama karesi için 'jpeg' + quality 60 bağlamı küçültür (ölçüldü 2026-09-16, aynı sayfa: scale 1'de png 8639 B / jpeg60 8691 B — kazanç yok; scale 0.5'te png 3603 B / jpeg60 3035 B). Asıl kaldıraç ÖLÇEKTİR; renk ve kenar keskinliğinin kendisi inceleniyorsa png'de kalın."),
|
|
253
257
|
quality: z.number().int().min(1).max(100).optional().describe("Yalnız format:'jpeg' ile: 1-100, doğrulama için 60 yeter. format 'png' iken verilirse çağrı reddedilir — PNG kayıpsızdır, kalite ayarı yoktur."),
|
|
@@ -690,10 +694,13 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
690
694
|
reject('PAGE-016', entry, schemaOpValidationErrors([entry]));
|
|
691
695
|
for (const entry of opRecords)
|
|
692
696
|
reject('PAGE-032', entry, codePropTemplateErrors([entry]));
|
|
693
|
-
if (opRecords.some(entry => entry.op === 'set_event')) {
|
|
697
|
+
if (opRecords.some(entry => entry.op === 'set_event' || entry.op === 'add_event')) {
|
|
694
698
|
const typesByElementId = new Map();
|
|
695
699
|
try {
|
|
696
|
-
|
|
700
|
+
const page = await readPage();
|
|
701
|
+
collectTypes(page.schema, typesByElementId);
|
|
702
|
+
for (const entry of opRecords)
|
|
703
|
+
reject('PAGE-034', entry, boundEventOps(page.schema, [entry]));
|
|
697
704
|
}
|
|
698
705
|
catch {
|
|
699
706
|
typesByElementId.clear();
|
|
@@ -705,6 +712,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
705
712
|
}
|
|
706
713
|
if (rejected.length > 0)
|
|
707
714
|
throw new PlatformApiError(rejectedOpsMessage(rejected), 400);
|
|
715
|
+
stripGatewayEventFields(opRecords);
|
|
708
716
|
const wantsRootAdd = opRecords.some(entry => entry.op === 'add_component' && (entry.parent === undefined || entry.parent === null || entry.parent === 'root'));
|
|
709
717
|
let retargeted = [];
|
|
710
718
|
if (wantsRootAdd) {
|
|
@@ -845,14 +853,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
845
853
|
// alanın gerçekten KAYDEDİLDİĞİNİ söylemez. Yamadan sonra şemayı bir kez okuyup op'un yazmak
|
|
846
854
|
// istediğiyle kayıtlı olanı karşılaştırıyoruz. Okuma başarısız olursa yama yine geçerlidir.
|
|
847
855
|
const contentOps = sentOps.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
|
|
848
|
-
let opDrops =
|
|
856
|
+
let opDrops = emptyWriteDiff();
|
|
849
857
|
if (contentOps.length > 0) {
|
|
850
858
|
try {
|
|
851
859
|
const saved = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
852
860
|
opDrops = schemaOpDrops(saved.schema, contentOps);
|
|
853
861
|
}
|
|
854
862
|
catch {
|
|
855
|
-
opDrops =
|
|
863
|
+
opDrops = emptyWriteDiff();
|
|
856
864
|
}
|
|
857
865
|
}
|
|
858
866
|
Object.assign(response, applicationTypeField(resolved));
|
|
@@ -881,7 +889,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
881
889
|
})));
|
|
882
890
|
server.registerTool('page_create', {
|
|
883
891
|
title: 'Sayfa oluştur',
|
|
884
|
-
description: "Yeni sayfa açar. Şemayı burada göndermek yerine boş bırakıp page_schema_patch micro-op'larıyla kurmak daha doğrudur (canlı canvas ve hedefli düzeltme orada). Şemayı burada gönderirseniz 2026-09-08'den beri AYNI doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile reddedilir ve sayfa HİÇ açılmaz. roleIds için role_list, layoutId için layout_list kullanın. Yanıttaki schema VARSAYILAN OLARAK iskelettir (çözülmüş layout düğümleri dâhil id/type/children'a iner) — tam gövde için view:'full'; gönderdiğiniz bir alan kaydedilmediyse silentlyDropped söyler.",
|
|
892
|
+
description: "Yeni sayfa açar. Uygulamada aynı ad ya da slug'da sayfa varsa varsayılan olarak YAZMAZ ve mevcut sayfanın id'sini döner (PAGE-035 — gönderilseydi platform '<ad> (1)' adıyla ikinci bir sayfa açardı); tekrar çalıştırılabilir yazma için ifExists:'update' verin, yanıttaki outcome 'created' | 'updated' olur. Mevcut sayfalar okunamazsa da yazmaz. Şemayı burada göndermek yerine boş bırakıp page_schema_patch micro-op'larıyla kurmak daha doğrudur (canlı canvas ve hedefli düzeltme orada). Şemayı burada gönderirseniz 2026-09-08'den beri AYNI doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile reddedilir ve sayfa HİÇ açılmaz. roleIds için role_list, layoutId için layout_list kullanın. Yanıttaki schema VARSAYILAN OLARAK iskelettir (çözülmüş layout düğümleri dâhil id/type/children'a iner) — tam gövde için view:'full'; gönderdiğiniz bir alan kaydedilmediyse silentlyDropped söyler.",
|
|
885
893
|
inputSchema: {
|
|
886
894
|
project: projectParam,
|
|
887
895
|
environment: environmentParam,
|
|
@@ -895,10 +903,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
895
903
|
middlewareIds: z.array(guid).optional(),
|
|
896
904
|
events: eventsParam.optional(),
|
|
897
905
|
schema: schemaParam.optional(),
|
|
898
|
-
view: writeViewParam
|
|
906
|
+
view: writeViewParam,
|
|
907
|
+
ifExists: ifExistsParam('sayfa', "mevcut sayfayı page_update gibi günceller — yalnız bu çağrıda VERİLEN alanlar yazılır, verilmeyen şema/olaylar/roller korunur (page_schema_patch ile kurulmuş şema betik tekrar koşunca silinmez); id'si ve flow'ları korunur")
|
|
899
908
|
},
|
|
900
909
|
annotations: writes
|
|
901
|
-
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view }) => {
|
|
910
|
+
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view, ifExists }) => {
|
|
902
911
|
const resolved = await resolveProfile(platform, applicationId);
|
|
903
912
|
const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
|
|
904
913
|
const container = ensurePageContainer(schema ?? [], resolved.profile.layout.rootContainer);
|
|
@@ -914,6 +923,45 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
914
923
|
const moved = blocksMeaningful ? reorderSchemaBlocks(container.schema) : [];
|
|
915
924
|
const gaps = (schema ?? []).length > 0 && blocksMeaningful ? verticalGapWarnings(container.schema, 'PAGE-010 (uyarı)') : [];
|
|
916
925
|
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: container.schema });
|
|
926
|
+
const existing = await existingMatch({
|
|
927
|
+
code: 'PAGE-035',
|
|
928
|
+
noun: 'sayfa',
|
|
929
|
+
where: 'bu uygulamada',
|
|
930
|
+
name,
|
|
931
|
+
slug,
|
|
932
|
+
idParam: 'pageId',
|
|
933
|
+
ifExists,
|
|
934
|
+
list: () => listAll(platform, project, environment, `/pages/${applicationId}`),
|
|
935
|
+
listTool: 'page_list',
|
|
936
|
+
copyCheck: "kullanılıp kullanılmadığını dependency_graph module:'page' ile kontrol edip gerekirse silin",
|
|
937
|
+
updateTool: 'pageId ile page_update'
|
|
938
|
+
});
|
|
939
|
+
if (existing !== undefined) {
|
|
940
|
+
const pageId = String(existing.id);
|
|
941
|
+
const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
|
|
942
|
+
const overrides = Object.fromEntries(Object.entries({
|
|
943
|
+
name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events,
|
|
944
|
+
schema: schema === undefined ? undefined : container.schema
|
|
945
|
+
}).filter(([, value]) => value !== undefined));
|
|
946
|
+
const body = pageUpdateBody(current, overrides);
|
|
947
|
+
const before = pageUpdateBody(current);
|
|
948
|
+
// Aynı ad/slug ile tekrar koşan betik o alanları "değişti" diye görmemeli (Gözlem 2).
|
|
949
|
+
const changed = Object.keys(overrides).filter(key => JSON.stringify(before[key]) !== JSON.stringify(overrides[key]));
|
|
950
|
+
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
951
|
+
const updateWarnings = schema === undefined
|
|
952
|
+
? []
|
|
953
|
+
: [...gaps, ...stringStyleWarning(stringStyles), ...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(container.schema)) : []), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'), ...profileWarnings];
|
|
954
|
+
const summary = {
|
|
955
|
+
id: pageId,
|
|
956
|
+
outcome: 'updated',
|
|
957
|
+
changed,
|
|
958
|
+
preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length },
|
|
959
|
+
...applicationTypeField(resolved)
|
|
960
|
+
};
|
|
961
|
+
if (updateWarnings.length > 0)
|
|
962
|
+
summary.layoutWarnings = updateWarnings;
|
|
963
|
+
return withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
|
|
964
|
+
}
|
|
917
965
|
const response = await platform.tenant('POST', project, `/pages/${applicationId}`, {
|
|
918
966
|
environment,
|
|
919
967
|
body: {
|
|
@@ -935,12 +983,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
935
983
|
// kaydedildi mi" sorusunu gövde değil bu diff cevaplar. `layout_create`'in yaptığının sayfa
|
|
936
984
|
// ikizi; Prop sarmalı açılmazsa doğru kaydedilmiş her prop düşmüş görünür (G-28).
|
|
937
985
|
const drops = (schema ?? []).length > 0
|
|
938
|
-
?
|
|
939
|
-
:
|
|
986
|
+
? writeDiff({ schema: unwrapProps(container.schema) }, { schema: unwrapProps(saved.schema) })
|
|
987
|
+
: emptyWriteDiff();
|
|
940
988
|
if (view !== 'full' && Array.isArray(saved.schema)) {
|
|
941
989
|
saved.schema = saved.schema.map(node => summarize(node));
|
|
942
990
|
}
|
|
943
|
-
const normalized = withWriteDiff(withNormalization(response, [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
|
|
991
|
+
const normalized = withWriteDiff(withNormalization(withOutcome(response, undefined), [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
|
|
944
992
|
const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(container.schema)) : []), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'), ...profileWarnings];
|
|
945
993
|
const typeField = applicationTypeField(resolved);
|
|
946
994
|
if (!normalized || typeof normalized !== 'object')
|
package/dist/tools/schemaOps.js
CHANGED
|
@@ -24,7 +24,7 @@ export function outlineWriteResponse(response) {
|
|
|
24
24
|
if (Array.isArray(container.appliedPatch))
|
|
25
25
|
container.appliedPatch = outlineAppliedPatch(container.appliedPatch);
|
|
26
26
|
}
|
|
27
|
-
export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue} | remove_event {target, event} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
|
|
27
|
+
export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue, replaceBoundFlows?} (olayın BÜTÜN bağlarını bu tek akışa indirir; olayda başka akış bağlıysa gateway PAGE-034 ile reddeder — bilerek değiştirmek için replaceBoundFlows: true) | add_event {target, event, eventValue} (olaya bir akış DAHA bağlar; bağlı akışların hepsi paralel ve birbirinden bağımsız koşar, sıra garantisi yoktur — sıra gerekiyorsa flow-trigger. Aynı akış iki kez 422 duplicate-event. Mobil runtime yalnız İLK akışı koşar) | remove_event {target, event, eventValue?} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. Olaya birden fazla akış bağlıysa eventValue ile hangisi olduğunu verin, yoksa 422 ambiguous-event. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
|
|
28
28
|
/**
|
|
29
29
|
* PAGE-008 — `set_event` uydurma bir olay adıyla 200 döner ve bağ SESSİZCE ölü kalır: motor olayı
|
|
30
30
|
* komponentin yaydığı adla eşleştirir, eşleşme yoksa akış hiç çalışmaz. Kontrol yalnız hedefin
|
|
@@ -35,7 +35,7 @@ export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listes
|
|
|
35
35
|
export function unknownEventOps(ops, typesByElementId, eventsBySlug) {
|
|
36
36
|
return ops
|
|
37
37
|
.map(op => op)
|
|
38
|
-
.filter(op => op.op === 'set_event')
|
|
38
|
+
.filter(op => op.op === 'set_event' || op.op === 'add_event')
|
|
39
39
|
.flatMap(op => {
|
|
40
40
|
const target = String(op.target ?? '');
|
|
41
41
|
const event = String(op.event ?? '');
|
package/dist/tools/workflows.js
CHANGED
|
@@ -7,10 +7,13 @@ import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
|
|
|
7
7
|
import { policyRules, microfunctionStepFindings, workflowStepFindings, undefinedInputFieldFindings } from '../knowledge/rules.js';
|
|
8
8
|
import { languageHeader } from '../knowledge/languageCodes.js';
|
|
9
9
|
import { handle } from '../platform/responses.js';
|
|
10
|
+
import { existingMatch, ifExistsParam, listAll, platformRename, withOutcome, withRename } from '../platform/ifExists.js';
|
|
11
|
+
import { outlineWrite, recordWriteViewParam } from '../platform/writeView.js';
|
|
10
12
|
import fastJsonPatch from 'fast-json-patch';
|
|
11
13
|
import { z } from 'zod';
|
|
12
14
|
import { constraintSummary } from '../knowledge/backendConstraints.js';
|
|
13
15
|
import { unmappedMembers, introducedUnmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
16
|
+
const workflowIfExistsParam = ifExistsParam('workflow', 'mevcut workflow\'u bu tanımla tam gövde günceller (id\'si korunur; workflow_update ile aynı yazma)');
|
|
14
17
|
const policyOverrideParam = z.boolean().optional()
|
|
15
18
|
.describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
|
|
16
19
|
const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
|
|
@@ -111,27 +114,51 @@ export function registerWorkflowTools(server, platform) {
|
|
|
111
114
|
}));
|
|
112
115
|
server.registerTool('workflow_create', {
|
|
113
116
|
title: 'Workflow oluştur',
|
|
114
|
-
description: "Yeni workflow tanımlar (definition = CreateWorkflowCommand). type: 'workflow' | 'automation' | 'middleware'. İlk kullanımdan önce platform_guide('workflow') okuyun: DTO-önce akışı, branch bağlama (branches[].nextStepId), tip×step matrisi. Her adım ve akışın kendisi 'description' taşımak ZORUNDADIR (WF-024, block): adımın NE İŞE YARADIĞINI 1-2 cümleyle yazın — Studio kanvasında ⓘ ikonu olarak görünür; mekanizmayı değil niyeti yazın. Middleware (istek öncesi koşan guard/setter, user değişkeni, alfabetik sıra) için platform_guide('middleware') + ngapps://examples/middleware-authenticate. Adımın gerçek bir tuzağı varsa (idempotent değil, kapsam adımda değil, dış bağımlılık, sessiz düşen dal) 'caution' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın; doldurma metin yazmayın. Gövde politika kontrolünden geçer. " + constraintSummary('CreateWorkflowCommand'),
|
|
115
|
-
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
117
|
+
description: "Yeni workflow tanımlar (definition = CreateWorkflowCommand). type: 'workflow' | 'automation' | 'middleware'. Uygulamada aynı ad ya da slug'da workflow varsa (tipten bağımsız) varsayılan olarak YAZMAZ ve mevcut kaydın id'sini döner (WF-034 — gönderilseydi platform '<ad> (1)' adıyla ikinci bir kayıt açardı); tekrar çalıştırılabilir yazma için ifExists:'update' verin, yanıttaki outcome 'created' | 'updated' olur. Mevcut workflow'lar okunamazsa da yazmaz. İlk kullanımdan önce platform_guide('workflow') okuyun: DTO-önce akışı, branch bağlama (branches[].nextStepId), tip×step matrisi. Her adım ve akışın kendisi 'description' taşımak ZORUNDADIR (WF-024, block): adımın NE İŞE YARADIĞINI 1-2 cümleyle yazın — Studio kanvasında ⓘ ikonu olarak görünür; mekanizmayı değil niyeti yazın. Middleware (istek öncesi koşan guard/setter, user değişkeni, alfabetik sıra) için platform_guide('middleware') + ngapps://examples/middleware-authenticate. Adımın gerçek bir tuzağı varsa (idempotent değil, kapsam adımda değil, dış bağımlılık, sessiz düşen dal) 'caution' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın; doldurma metin yazmayın. Gövde politika kontrolünden geçer. " + constraintSummary('CreateWorkflowCommand'),
|
|
118
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, ifExists: workflowIfExistsParam, view: recordWriteViewParam, policyOverride: policyOverrideParam },
|
|
116
119
|
annotations: writes
|
|
117
|
-
}, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
|
|
120
|
+
}, handle(config, async ({ project, environment, applicationId, definition, ifExists, view, policyOverride }) => {
|
|
118
121
|
const normalized = normalizeWorkflowDefinition(definition);
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
// Politika ÖNCE koşar: block alan istek platforma hiç gitmez, var-mı listesi de okunmaz.
|
|
123
|
+
const stepFindings = [
|
|
121
124
|
...await microfunctionStepFindings(platform, project, environment, definition, definition.type, policyOverride),
|
|
122
125
|
...await workflowStepFindings(platform, project, environment, applicationId, definition, definition.type, policyOverride),
|
|
123
126
|
...await undefinedInputFieldFindings(platform, project, environment, definition, definition.type, policyOverride)
|
|
124
|
-
]
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
127
|
+
];
|
|
128
|
+
const bodyFindings = (operation) => evaluatePolicy(config, policyRules, 'workflow', definition, { operation, workflowKind: definition.type }, policyOverride);
|
|
129
|
+
const createFindings = enforcePolicy([...bodyFindings('create'), ...stepFindings]);
|
|
130
|
+
const existing = await existingMatch({
|
|
131
|
+
code: 'WF-034',
|
|
132
|
+
noun: 'workflow',
|
|
133
|
+
where: 'bu uygulamada',
|
|
134
|
+
name: typeof definition.name === 'string' ? definition.name : undefined,
|
|
135
|
+
slug: typeof definition.slug === 'string' ? definition.slug : undefined,
|
|
136
|
+
id: typeof definition.id === 'string' ? definition.id : undefined,
|
|
137
|
+
idParam: 'definition.id',
|
|
138
|
+
ifExists,
|
|
139
|
+
list: () => listAll(platform, project, environment, `/workflows/${applicationId}`),
|
|
140
|
+
listTool: 'workflow_list',
|
|
141
|
+
copyCheck: 'kullanılıp kullanılmadığını workflow_dependencies ile kontrol edip gerekirse silin',
|
|
142
|
+
updateTool: 'id ile workflow_update',
|
|
143
|
+
kind: { key: 'type', value: definition.type }
|
|
144
|
+
});
|
|
145
|
+
// Kayıt bulunduysa yazma bir UPDATE'tir: gövde kuralları workflow_update ile aynı kipte yeniden
|
|
146
|
+
// değerlendirilir (R-1). Adım denetimleri operasyondan bağımsızdır, tekrar koşmaz.
|
|
147
|
+
const findings = existing === undefined ? createFindings : enforcePolicy([...bodyFindings('update'), ...stepFindings]);
|
|
148
|
+
const drift = unmappedMembers(existing === undefined ? 'CreateWorkflowCommand' : 'UpdateWorkflowCommand', definition);
|
|
149
|
+
const payload = existing === undefined
|
|
150
|
+
? await platform.tenant('POST', project, `/workflows/${applicationId}`, { environment, body: definition })
|
|
151
|
+
: await platform.tenant('PUT', project, `/workflows/${applicationId}/${String(existing.id)}`, { environment, body: definition });
|
|
152
|
+
const saved = payload?.data;
|
|
153
|
+
const rename = ifExists === 'duplicate' ? platformRename(definition, saved, 'dash') : { sent: definition };
|
|
154
|
+
return attachFindings(withUnmappedMembers(withWriteDiff(withRename(withNormalization(withOutcome(outlineWrite(payload, view, ['type']), existing), normalized), rename.renamed), writeDiff(rename.sent, payload)), drift), findings);
|
|
128
155
|
}));
|
|
129
156
|
server.registerTool('workflow_update', {
|
|
130
157
|
title: 'Workflow güncelle',
|
|
131
158
|
description: 'Workflow tanımını günceller (definition = UpdateWorkflowCommand, tam gövde). Hedefli değişiklik için workflow_patch tercih edin. Her adım ve akışın kendisi \'description\' taşımak ZORUNDADIR (WF-024, block). Adımın gerçek bir tuzağı varsa (idempotent değil, kapsam adımda değil, dış bağımlılık, sessiz düşen dal) \'caution\' alanına yazın — OPSİYONELDİR, tuzak yoksa boş bırakın; doldurma metin yazmayın. Gövde politika kontrolünden geçer. ' + constraintSummary('UpdateWorkflowCommand'),
|
|
132
|
-
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
159
|
+
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, definition: definitionParam, view: recordWriteViewParam, policyOverride: policyOverrideParam },
|
|
133
160
|
annotations: updates
|
|
134
|
-
}, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
|
|
161
|
+
}, handle(config, async ({ project, environment, applicationId, id, definition, view, policyOverride }) => {
|
|
135
162
|
const normalized = normalizeWorkflowDefinition(definition);
|
|
136
163
|
const findings = enforcePolicy([
|
|
137
164
|
...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'update', workflowKind: definition.type }, policyOverride),
|
|
@@ -141,7 +168,7 @@ export function registerWorkflowTools(server, platform) {
|
|
|
141
168
|
]);
|
|
142
169
|
const drift = unmappedMembers('UpdateWorkflowCommand', definition);
|
|
143
170
|
const payload = await platform.tenant('PUT', project, `/workflows/${applicationId}/${id}`, { environment, body: definition });
|
|
144
|
-
return attachFindings(withUnmappedMembers(withWriteDiff(withNormalization(payload, normalized), writeDiff(definition, payload)), drift), findings);
|
|
171
|
+
return attachFindings(withUnmappedMembers(withWriteDiff(withNormalization(outlineWrite(payload, view, ['type']), normalized), writeDiff(definition, payload)), drift), findings);
|
|
145
172
|
}));
|
|
146
173
|
server.registerTool('workflow_patch', {
|
|
147
174
|
title: 'Workflow\'a patch uygula',
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appaflytech/zeyrek-ai-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Zeyrek (NgApps) platform APIs as MCP tools — data, logic and Studio design surface in one gateway",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/zeyrek-v2/zeyrek-app.git",
|
|
10
|
+
"directory": "zeyrek-mcp"
|
|
11
|
+
},
|
|
7
12
|
"engines": {
|
|
8
13
|
"node": ">=20"
|
|
9
14
|
},
|
|
@@ -55,4 +60,4 @@
|
|
|
55
60
|
"typescript": "^5.7.2",
|
|
56
61
|
"vitest": "^3.0.0"
|
|
57
62
|
}
|
|
58
|
-
}
|
|
63
|
+
}
|