@appaflytech/zeyrek-ai-mcp 0.0.10 → 0.0.12
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 +11 -6
- package/dist/knowledge/designSnapshot.js +77 -0
- package/dist/knowledge/errorPath.js +48 -5
- package/dist/knowledge/normalize.js +47 -0
- package/dist/knowledge/pageDiscover.js +151 -0
- package/dist/knowledge/rules.js +163 -0
- package/dist/knowledge/themeDiscover.js +884 -0
- package/dist/knowledge/themeResolve.js +4 -2
- package/dist/platform/errors.js +76 -1
- package/dist/platform/responses.js +12 -1
- package/dist/resources/examples/design-snapshot.json +157 -0
- package/dist/resources/guides/theme.md +84 -1
- package/dist/resources/guides/workflow.md +120 -9
- package/dist/tools/automations.js +7 -4
- package/dist/tools/flows.js +119 -17
- package/dist/tools/pages.js +16 -0
- package/dist/tools/themes.js +52 -0
- package/dist/tools/workflows.js +6 -2
- package/package.json +2 -1
package/dist/tools/flows.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fetchHost, flowPath, hostParam, unwrap } from './screens.js';
|
|
2
2
|
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive, microOps } from './common.js';
|
|
3
3
|
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
4
|
-
import { normalizeFlowSteps, targetlessSetterWarnings, unknownSetterTargetWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
4
|
+
import { flowBindings, normalizeFlowSteps, targetlessSetterWarnings, unknownSetterTargetWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
5
5
|
import { flowBodyWarnings } from '../knowledge/coverage.js';
|
|
6
6
|
import { PlatformApiError } from '../platform/errors.js';
|
|
7
7
|
import { handle } from '../platform/responses.js';
|
|
@@ -50,6 +50,90 @@ function flowSummary(flow) {
|
|
|
50
50
|
function hostNodes(record) {
|
|
51
51
|
return record.schema ?? record.views ?? [];
|
|
52
52
|
}
|
|
53
|
+
const BINDINGS_UNAVAILABLE = 'Barındırıcı kaydı okunamadığı için olay bağları hesaplanamadı; akış listesi yine de günceldir.';
|
|
54
|
+
/**
|
|
55
|
+
* Akış listesine bağları ekler. `danglingBindings` ters yönü söyler: şemada id'si geçen ama
|
|
56
|
+
* barındırıcıda karşılığı olmayan akış — olay tetiklenir, hiçbir şey koşmaz.
|
|
57
|
+
*/
|
|
58
|
+
function flowListResult(flows, record) {
|
|
59
|
+
if (record === undefined)
|
|
60
|
+
return { flows: flows.map(flowSummary), bindingsNote: BINDINGS_UNAVAILABLE };
|
|
61
|
+
const bindings = flowBindings(hostNodes(record), record.events);
|
|
62
|
+
const known = new Set(flows.map(flow => String(flow.id).toLowerCase()));
|
|
63
|
+
const danglingBindings = [...bindings.entries()]
|
|
64
|
+
.filter(([flowId]) => !known.has(flowId))
|
|
65
|
+
.flatMap(([flowId, entries]) => entries.map(entry => ({ flowId, ...entry })));
|
|
66
|
+
return {
|
|
67
|
+
flows: flows.map(flow => ({ ...flowSummary(flow), bindings: bindings.get(String(flow.id).toLowerCase()) ?? [] })),
|
|
68
|
+
unboundFlowIds: flows.map(flow => String(flow.id)).filter(flowId => !bindings.has(flowId.toLowerCase())),
|
|
69
|
+
...(danglingBindings.length > 0 ? { danglingBindings } : {})
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const ifExistsParam = z.enum(['error', 'update', 'duplicate']).default('error').describe("Barındırıcıda aynı ad ya da slug'da (büyük/küçük harf duyarsız) akış varsa ne olacağı. 'error' (varsayılan): yazmaz, mevcut akışın id'sini döner. 'update': mevcut akışı bu gövdeyle günceller (id'si ve olay bağları korunur). 'duplicate': platformun ürün davranışı — ikinci kayıt açılır, platform ada/slug'a ' (1)' sayacını ekler (Studio'daki 'New Flow (1)' ile aynı kural).");
|
|
73
|
+
const upper = (value) => String(value ?? '').toUpperCase();
|
|
74
|
+
/**
|
|
75
|
+
* Barındırıcının akışları, önbelleksiz liste ucundan (`GET /pages|layouts/{id}/flows`). Layout ve
|
|
76
|
+
* sayfa tasarım kaydı önbellekten gelir ve zarfsız akış yazmasından sonra akışı hemen taşımaz;
|
|
77
|
+
* 2026-09-16 raporundaki kopyalar bu yüzden doğdu. Widget kaydı önbelleksizdir, oradan okunur.
|
|
78
|
+
* Layout ucu olmayan eski backend 404/405 verir: o zaman kayda düşülür.
|
|
79
|
+
*/
|
|
80
|
+
async function listHostFlows(platform, host, project, environment, applicationId, hostId, hostRecord) {
|
|
81
|
+
if (host !== 'component') {
|
|
82
|
+
try {
|
|
83
|
+
const payload = await platform.tenant('GET', project, flowPath(host, applicationId, hostId), { environment });
|
|
84
|
+
if (!Array.isArray(payload?.data))
|
|
85
|
+
throw new PlatformApiError('akış listesi yanıtında data dizisi yok');
|
|
86
|
+
return payload.data;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const status = error instanceof PlatformApiError ? error.status : undefined;
|
|
90
|
+
if (host === 'page' || (status !== 404 && status !== 405))
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const record = await hostRecord();
|
|
95
|
+
return Array.isArray(record.flows) ? record.flows : [];
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* FLOW-026 kapısı. Aynı adla ikinci akışın `"<ad> (1)"` olarak açılması platformun ürün kararıdır
|
|
99
|
+
* (`FlowService.MakeNameUniqueAsync`; Studio'da da sayfa/workflow/akış aynı kuralla adlanır) ve
|
|
100
|
+
* değişmez. MCP ajanı ise çağrıyı tekrar edebildiği için varsayılanı "yazma, mevcut id'yi söyle"dir.
|
|
101
|
+
* Aday araması platformda `LIKE` ile harfe duyarlıdır; gateway bilinçli olarak harf duyarsız
|
|
102
|
+
* karşılaştırır. Var-mı okuması başarısız olursa çağrı düşer — okuyamadığı listeyi boş sayan kontrol
|
|
103
|
+
* tam da zaman aşımında kopya üretirdi.
|
|
104
|
+
*/
|
|
105
|
+
async function existingFlowMatch(platform, host, project, environment, applicationId, hostId, hostRecord, name, slug, flowId, ifExists) {
|
|
106
|
+
if (ifExists === 'duplicate')
|
|
107
|
+
return undefined;
|
|
108
|
+
let flows;
|
|
109
|
+
try {
|
|
110
|
+
flows = await listHostFlows(platform, host, project, environment, applicationId, hostId, async () => hostRecord);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
throw new PlatformApiError(`FLOW-026: barındırıcının mevcut akışları okunamadı, akış YAZILMADI (${error instanceof Error ? error.message : String(error)}). Liste okunmadan yazmak aynı adla ikinci akış açabilir; flow_list ile tekrar deneyin ya da bilerek ikinci akış istiyorsanız ifExists:'duplicate' verin.`);
|
|
114
|
+
}
|
|
115
|
+
const matches = flows.filter(flow => upper(flow.name) === upper(name) || upper(flow.slug) === upper(slug));
|
|
116
|
+
const [match] = matches;
|
|
117
|
+
if (match === undefined)
|
|
118
|
+
return undefined;
|
|
119
|
+
const describe = (flow) => `${String(flow.id)} ('${String(flow.name)}', slug '${String(flow.slug)}')`;
|
|
120
|
+
const copyPattern = new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} \\(\\d+\\)$`, 'i');
|
|
121
|
+
const copies = flows.filter(flow => copyPattern.test(String(flow.name ?? '')));
|
|
122
|
+
const copyNote = copies.length > 0
|
|
123
|
+
? ` Barındırıcıda bu adın sayaçlı eşleri de var: ${copies.map(describe).join(' · ')} — bağı olup olmadığını flow_list (bindings) ile kontrol edip gerekirse silin.`
|
|
124
|
+
: '';
|
|
125
|
+
if (ifExists === 'error') {
|
|
126
|
+
const where = host === 'page' ? 'sayfada' : host === 'layout' ? "layout'ta" : "widget'ta";
|
|
127
|
+
throw new PlatformApiError(`FLOW-026: bu ${where} aynı ad ya da slug'da akış zaten var: ${matches.map(describe).join(' · ')}. İstek gönderilmedi — gönderilseydi platform ürün kuralı gereği ikinci bir akış açardı (çakışan ad ya da slug'a ' (1)' sayacı eklenerek; yalnız harf farkı varsa sayaçsız). Mevcut akışı güncellemek için aynı çağrıyı ifExists:'update' ile tekrarlayın; bilerek ikinci akış istiyorsanız ifExists:'duplicate'.${copyNote}`, 409);
|
|
128
|
+
}
|
|
129
|
+
if (matches.length > 1) {
|
|
130
|
+
throw new PlatformApiError(`FLOW-026: ad ve slug farklı akışlara çıkıyor (${matches.map(describe).join(' · ')}); hangisinin güncelleneceği belirsiz, istek gönderilmedi. flowId ile flow_update kullanın.${copyNote}`, 409);
|
|
131
|
+
}
|
|
132
|
+
if (flowId !== undefined && flowId.toLowerCase() !== String(match.id).toLowerCase()) {
|
|
133
|
+
throw new PlatformApiError(`FLOW-026: verilen flowId (${flowId}) aynı adlı mevcut akışla (${describe(match)}) çelişiyor; istek gönderilmedi. Mevcut akışı güncellemek için flowId'yi vermeyin ya da ${String(match.id)} verin.`, 409);
|
|
134
|
+
}
|
|
135
|
+
return match;
|
|
136
|
+
}
|
|
53
137
|
async function requiredParameterWarnings(platform, project, environment, applicationId, steps) {
|
|
54
138
|
const warnings = [];
|
|
55
139
|
const microserviceSteps = steps
|
|
@@ -226,32 +310,41 @@ export function registerFlowTools(server, platform) {
|
|
|
226
310
|
const config = platform.config;
|
|
227
311
|
server.registerTool('flow_list', {
|
|
228
312
|
title: 'Akışları listele',
|
|
229
|
-
description: "Barındırıcının olay
|
|
313
|
+
description: "Barındırıcının olay akışlarını, her birine bağlı olaylarla (bindings: [{ target: elementId | 'page', event }]) listeler. unboundFlowIds hiçbir olaya bağlı olmayan (sahipsiz) akışlardır; danglingBindings şemada id'si geçen ama barındırıcıda olmayan akışlardır. Sayfa ve layout akışları önbelleksiz liste ucundan, widget akışları kaydın kendi gövdesinden okunur; bağlar barındırıcının şemasından hesaplanır.",
|
|
230
314
|
inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid },
|
|
231
315
|
annotations: readOnly
|
|
232
316
|
}, handle(config, async ({ project, environment, host, applicationId, hostId }) => {
|
|
233
|
-
if (host === '
|
|
234
|
-
const
|
|
235
|
-
return
|
|
317
|
+
if (host === 'component') {
|
|
318
|
+
const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
319
|
+
return flowListResult(Array.isArray(record.flows) ? record.flows : [], record);
|
|
236
320
|
}
|
|
237
|
-
|
|
238
|
-
|
|
321
|
+
let record;
|
|
322
|
+
const readRecord = async () => (record ??= await fetchHost(platform, host, project, environment, applicationId, hostId));
|
|
323
|
+
const flows = await listHostFlows(platform, host, project, environment, applicationId, hostId, readRecord);
|
|
324
|
+
return flowListResult(flows, record ?? await readRecord().catch(() => undefined));
|
|
239
325
|
}));
|
|
240
326
|
server.registerTool('flow_get', {
|
|
241
327
|
title: 'Akış getir',
|
|
242
|
-
description: "Tek akışı adımlarıyla getirir; yeni akış yazarken en iyi şablon budur. Hedefi silinmiş (element'siz) setter iteratee'si varsa yanıttaki flowWarnings söyler — o adım koşar ama hiçbir şeye yazmaz.",
|
|
328
|
+
description: "Tek akışı adımlarıyla getirir; yeni akış yazarken en iyi şablon budur. bindings bu akışa bağlı olaylardır ([{ target: elementId | 'page', event }]; boşsa akış sahipsizdir). Hedefi silinmiş (element'siz) setter iteratee'si varsa yanıttaki flowWarnings söyler — o adım koşar ama hiçbir şeye yazmaz.",
|
|
243
329
|
inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
|
|
244
330
|
annotations: readOnly
|
|
245
331
|
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
|
|
246
332
|
const response = await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment });
|
|
333
|
+
const record = await fetchHost(platform, host, project, environment, applicationId, hostId).catch(() => undefined);
|
|
247
334
|
const warnings = targetlessSetterWarnings(response);
|
|
248
|
-
if (
|
|
335
|
+
if (!response || typeof response !== 'object' || Array.isArray(response))
|
|
249
336
|
return response;
|
|
250
|
-
return {
|
|
337
|
+
return {
|
|
338
|
+
...response,
|
|
339
|
+
...(record === undefined
|
|
340
|
+
? { bindingsNote: BINDINGS_UNAVAILABLE }
|
|
341
|
+
: { bindings: flowBindings(hostNodes(record), record.events).get(flowId.toLowerCase()) ?? [] }),
|
|
342
|
+
...(warnings.length > 0 ? { flowWarnings: warnings } : {})
|
|
343
|
+
};
|
|
251
344
|
}));
|
|
252
345
|
server.registerTool('flow_create', {
|
|
253
346
|
title: 'Akış oluştur',
|
|
254
|
-
description: "Sayfa/layout/komponent için olay akışı oluşturur. İstek yalnız akışı yazar, barındırıcının şemasına dokunmaz; gateway barındırıcıyı
|
|
347
|
+
description: "Sayfa/layout/komponent için olay akışı oluşturur. Aynı ad ya da slug'da akış varsa varsayılan olarak YAZMAZ ve mevcut akışın id'sini döner (FLOW-026 — gönderilseydi platform ürün kuralı gereği '<ad> (1)' adıyla ikinci bir akış açardı); tekrar çalıştırılabilir yazma için ifExists:'update' verin, yanıttaki outcome 'created' | 'updated' olur. Mevcut akışlar okunamazsa da yazmaz. İstek yalnız akışı yazar, barındırıcının şemasına dokunmaz; gateway barındırıcıyı setter hedeflerini denetlemek (FLOW-024), mevcut akışları ise önbelleksiz liste ucundan okur. Akışı bir komponent olayına bağlamak ayrı adımdır ve akış kaydedildikten SONRA yapılır: set_event {target, event, eventValue: <flow id>} — sayfada page_schema_patch, layout'ta layout_schema_patch, widget'ta component_schema_patch. Hangi olayların bağlı olduğunu flow_list / flow_get bindings alanı söyler.",
|
|
255
348
|
inputSchema: {
|
|
256
349
|
project: projectParam,
|
|
257
350
|
environment: environmentParam,
|
|
@@ -263,12 +356,13 @@ export function registerFlowTools(server, platform) {
|
|
|
263
356
|
steps: stepsParam,
|
|
264
357
|
errorSteps: stepsParam.optional(),
|
|
265
358
|
flowId: guid.optional().describe('Verilmezse platform üretir; şemaya önceden bağlamak için kendiniz üretebilirsiniz'),
|
|
359
|
+
ifExists: ifExistsParam,
|
|
266
360
|
policyOverride: policyOverrideParam
|
|
267
361
|
},
|
|
268
362
|
annotations: writes
|
|
269
|
-
}, handle(config, async ({ project, environment, host, applicationId, hostId, name, slug, steps, errorSteps, flowId, policyOverride }) => {
|
|
363
|
+
}, handle(config, async ({ project, environment, host, applicationId, hostId, name, slug, steps, errorSteps, flowId, ifExists, policyOverride }) => {
|
|
270
364
|
const hostRecord = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
271
|
-
const resolvedFlowId = flowId ?? randomUUID();
|
|
365
|
+
const resolvedFlowId = (flowId ?? randomUUID()).toLowerCase();
|
|
272
366
|
const flow = {
|
|
273
367
|
id: resolvedFlowId,
|
|
274
368
|
name,
|
|
@@ -279,6 +373,8 @@ export function registerFlowTools(server, platform) {
|
|
|
279
373
|
};
|
|
280
374
|
const normalized = [...normalizeFlowSteps(flow.steps), ...normalizeFlowSteps(flow.errorSteps)];
|
|
281
375
|
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'create' }, policyOverride));
|
|
376
|
+
const existing = await existingFlowMatch(platform, host, project, environment, applicationId, hostId, hostRecord, name, slug, flowId, ifExists);
|
|
377
|
+
const writtenFlowId = existing === undefined ? resolvedFlowId : String(existing.id);
|
|
282
378
|
const parameterWarnings = [
|
|
283
379
|
...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
|
|
284
380
|
...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
|
|
@@ -287,15 +383,21 @@ export function registerFlowTools(server, platform) {
|
|
|
287
383
|
];
|
|
288
384
|
// Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
|
|
289
385
|
// ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
|
|
290
|
-
// eskitir. Kayıt
|
|
291
|
-
|
|
386
|
+
// eskitir. Kayıt FLOW-024 hedef araması ve FLOW-026 var-mı kontrolü için okunur.
|
|
387
|
+
if (existing === undefined) {
|
|
388
|
+
await platform.tenant('POST', project, flowPath(host, applicationId, hostId), { environment, body: { flow } });
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, writtenFlowId), { environment, body: { flow: { ...flow, id: writtenFlowId } } });
|
|
392
|
+
}
|
|
292
393
|
// Diff YALNIZ çağıranın yazdığı alanları kapsar; `id` ve barındırıcı bağı (`pageId` vb.)
|
|
293
394
|
// gövdeye gateway koyar, kullanıcının düzeltebileceği bir şey değildir.
|
|
294
|
-
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId,
|
|
395
|
+
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, writtenFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps });
|
|
295
396
|
return attachFindings(withWriteDiff(withNormalization({
|
|
296
397
|
host,
|
|
297
398
|
hostId,
|
|
298
|
-
|
|
399
|
+
outcome: existing === undefined ? 'created' : 'updated',
|
|
400
|
+
flow: { id: writtenFlowId, name, slug, steps: steps.length },
|
|
299
401
|
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
300
402
|
hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]. Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
|
|
301
403
|
}, normalized), drops), findings);
|
package/dist/tools/pages.js
CHANGED
|
@@ -19,6 +19,8 @@ import { resolveIdentityToken } from '../platform/tokenVault.js';
|
|
|
19
19
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
20
20
|
import { dirname, resolve } from 'node:path';
|
|
21
21
|
import { setPropTypeErrors } from '../knowledge/propTypes.js';
|
|
22
|
+
import { designSnapshotInput } from '../knowledge/designSnapshot.js';
|
|
23
|
+
import { discoverPage } from '../knowledge/pageDiscover.js';
|
|
22
24
|
import { z } from 'zod';
|
|
23
25
|
/**
|
|
24
26
|
* Runtime paketindeki `appFunctions`/`pageFunctions` (UiFunctionRuntimeDto: id, name, parameters,
|
|
@@ -800,6 +802,20 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
800
802
|
...flexIndexOrderNote(ordering.moved)
|
|
801
803
|
]), opDrops);
|
|
802
804
|
}));
|
|
805
|
+
server.registerTool('page_discover', {
|
|
806
|
+
title: 'Tasarım ekranından sayfa iskeleti öner',
|
|
807
|
+
description: "Tasarım anlık görüntüsündeki bir EKRANIN rolünden (list · form · detail · auth · shell — sözleşme platform_guide('theme') §Tasarım dosyasından tema) altın örnek arketipini seçip page_create + page_schema_patch iskeletini döner: pageCreateArgs (ad/slug ekrandan), schemaPatchOps, flows (örneğin akış gövdeleri), presetBindings (theme_discover'ın aynı anlık görüntüden ürettiği adlı preset TEK adaysa props.style.$preset olarak bağlanır), notes (yerleşim + ekran notları), caveats (yer tutucular, doldurulacak alanlar), next (çağrı sırası). shell rolü layout_create iskeleti döner. PİKSEL KOPYASI DEĞİLDİR: iskelet örneğin domainindedir, ajan ekran notlarına göre tamamlar. YAZMAZ, platforma gitmez, tahmin yapmaz.",
|
|
808
|
+
inputSchema: {
|
|
809
|
+
snapshot: designSnapshotInput.describe('Tasarım anlık görüntüsü (screens[] dolu olmalı)'),
|
|
810
|
+
screen: z.string().min(1).describe("screens[] içindeki ekranın nodeId'si ('92:4339') ya da adı ('Deals')"),
|
|
811
|
+
layoutId: guid.optional().describe('Sayfanın kabuğu (layout_list); verilmezse pageCreateArgs kabuksuz döner ve caveats söyler'),
|
|
812
|
+
middlewareIds: z.array(guid).optional().describe('Korumalı sayfa için auth middleware id listesi (middleware_list); verilmezse örnekteki yer tutucu düşer ve caveats söyler')
|
|
813
|
+
},
|
|
814
|
+
annotations: readOnly
|
|
815
|
+
}, handle(config, async ({ snapshot, screen, layoutId, middlewareIds }) => discoverPage(snapshot, screen, {
|
|
816
|
+
...(layoutId === undefined ? {} : { layoutId }),
|
|
817
|
+
...(middlewareIds === undefined ? {} : { middlewareIds })
|
|
818
|
+
})));
|
|
803
819
|
server.registerTool('page_create', {
|
|
804
820
|
title: 'Sayfa oluştur',
|
|
805
821
|
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.",
|
package/dist/tools/themes.js
CHANGED
|
@@ -2,6 +2,8 @@ import { guid, projectParam, readOnly, updates } from './common.js';
|
|
|
2
2
|
import { diffCssVars, mergeRadiusScale, mergeSpacingScale, mergeTypeScale, resolveTheme } from '../knowledge/themeResolve.js';
|
|
3
3
|
import { diffThemePaths, mergeTheme, normalizeTheme, readProjectTheme, summarizeTheme, themeInput, unsetThemePaths } from '../knowledge/themeSchema.js';
|
|
4
4
|
import { componentPresetTypeWarnings, themeInputWarnings, unsetWarnings } from '../knowledge/themeRules.js';
|
|
5
|
+
import { designSnapshotInput } from '../knowledge/designSnapshot.js';
|
|
6
|
+
import { discoverTheme } from '../knowledge/themeDiscover.js';
|
|
5
7
|
import { attachFindings, enforcePolicy, evaluatePolicy, applyPolicyMode } from '../knowledge/policy.js';
|
|
6
8
|
import { projectUpdateForm } from './projects.js';
|
|
7
9
|
import { withNormalization } from '../knowledge/normalize.js';
|
|
@@ -90,6 +92,56 @@ export function registerThemeTools(server, platform) {
|
|
|
90
92
|
note: "Değerlerin ekrana indiği hâl için view:'resolved'; yazmak için theme_update (kısmi günceller, lint'ten geçirir, hangi CSS değişkeninin değiştiğini söyler). Adlı bir preset'i düğüme bağlamak: style.$preset — platform_guide('theme')."
|
|
91
93
|
};
|
|
92
94
|
}));
|
|
95
|
+
server.registerTool('theme_discover', {
|
|
96
|
+
title: 'Tasarım anlık görüntüsünden tema öner',
|
|
97
|
+
description: "Bir tasarım anlık görüntüsünü (Figma değişkenleri, metin stilleri, gölgeler, bileşen ölçüleri — sözleşme platform_guide('theme') §Tasarım dosyasından tema, altın örnek design-snapshot) theme_update şemasında bir tema önerisine çevirir ve HESABINI verir: mapping (hangi ad hangi yola, hangi kuralla), unmapped (neden gitmedi), gaps (kaynağı olmayan alan), lint uyarıları; project verilirse mevcut temaya göre changed + cssVarsChanged önizlemesi. YAZMAZ — yazmak için theme_update({ theme }), önce dryRun. Eşleme ad temelli ve deterministiktir, tahmin yapmaz: eşlenmeyen token için kararı anlık görüntünün hints alanına yazarsınız ve karar dosyada kalır — Figma'ya ikinci kez gidilmez. Yalnız sapan değer yazılır: platform varsayılanına eşit taban ve ±0,05 içindeki oran öneriye girmez.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
snapshot: designSnapshotInput.describe("Tasarım anlık görüntüsü — sözleşme platform_guide('theme') §Tasarım dosyasından tema; her seviyede strict, tanınmayan anahtar reddedilir. variables get_variable_defs çıktısının kendisidir; hints: Figma adı → tema yolu (accent.muted, paletteDark.accent.primary, colors.blue.500) ya da 'ignore'"),
|
|
100
|
+
project: projectParam.describe("Yalnız ÖNİZLEME için: verilirse projenin mevcut teması okunur (GET) ve öneri ona derin birleştirilerek changed + cssVarsChanged hesaplanır. Verilmezse platforma HİÇ gidilmez — DefaultProject burada devreye girmez"),
|
|
101
|
+
deriveDark: z.boolean().optional().describe("true: anlık görüntüde koyu küme (variablesDark) YOKSA paletteDark aydınlık accent/status değerlerinden deterministik karışımla türetilir ve her değer mapping.via:'derived' ile işaretlenir — marka kararı değil başlangıç noktası. variablesDark varsa bayrak yok sayılır. Varsayılan: türetme yok (gaps: paletteDark, THEME-009)")
|
|
102
|
+
},
|
|
103
|
+
annotations: readOnly
|
|
104
|
+
}, handle(config, async ({ snapshot, project, deriveDark }) => {
|
|
105
|
+
const discovered = discoverTheme(snapshot, { deriveDark: deriveDark === true });
|
|
106
|
+
const proposal = normalizeTheme(discovered.theme);
|
|
107
|
+
const fixes = [...discovered.normalized, ...proposal.fixes];
|
|
108
|
+
let preview;
|
|
109
|
+
let merged = proposal.theme;
|
|
110
|
+
if (project !== undefined) {
|
|
111
|
+
const { project: projectInfo, theme: base } = await loadProject(platform, project);
|
|
112
|
+
const current = base ?? {};
|
|
113
|
+
merged = normalizeTheme(mergeTheme(current, proposal.theme)).theme;
|
|
114
|
+
preview = {
|
|
115
|
+
project: projectInfo,
|
|
116
|
+
changed: diffThemePaths(current, merged),
|
|
117
|
+
cssVarsChanged: diffCssVars(resolveTheme(current).cssVars, resolveTheme(merged).cssVars),
|
|
118
|
+
note: 'theme_update({ theme, dryRun: true }) aynı sonucu verir; bu önizleme yalnız GET ile hesaplandı, hiçbir şey yazılmadı.'
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Uyarılar YAZMA anındaki kurallarla aynı motordan (THEME-005/008/009 …): ajan öneriyi
|
|
122
|
+
// theme_update'e taşımadan ne uyarı alacağını burada görür. Block kuralları da listelenir ama
|
|
123
|
+
// enforce edilmez — bu tool yazmıyor; yazma anında theme_update durdurur. Operasyon 'update'
|
|
124
|
+
// çünkü tema kuralları yalnız ona kayıtlı (`appliesTo: ['update']`); 'preview' ile hiçbiri
|
|
125
|
+
// değerlendirilmiyordu (inceleme B-7). Sorulan şey aynı: "bu kayıt yazılsa geçer mi".
|
|
126
|
+
const findings = config.policyMode === 'off'
|
|
127
|
+
? []
|
|
128
|
+
: applyPolicyMode(config, [
|
|
129
|
+
...evaluatePolicy(config, policyRules, 'theme', merged, { operation: 'update' }),
|
|
130
|
+
...themeInputWarnings(proposal.theme, merged)
|
|
131
|
+
]);
|
|
132
|
+
const empty = Object.keys(proposal.theme).length === 0;
|
|
133
|
+
return attachFindings(withNormalization({
|
|
134
|
+
source: snapshot.source,
|
|
135
|
+
theme: proposal.theme,
|
|
136
|
+
mapping: discovered.mapping,
|
|
137
|
+
unmapped: discovered.unmapped,
|
|
138
|
+
gaps: discovered.gaps,
|
|
139
|
+
...(preview === undefined ? {} : { preview }),
|
|
140
|
+
note: empty
|
|
141
|
+
? 'Anlık görüntüde eşlenebilir token yok — unmapped (sebep) ve gaps (kaynağı olmayan alan) listelerine bakın; kararı hints ile verin.'
|
|
142
|
+
: "Yazmak için theme_update({ theme }) — önce dryRun: true. unmapped kararlarını anlık görüntünün hints alanına yazın ve dosyayı koşu klasöründe bırakın; ikinci koşu Figma'ya dönmez. gaps'teki alanlar uydurulmadı: kaynağı ekleyin ya da boş bırakın."
|
|
143
|
+
}, fixes), findings);
|
|
144
|
+
}));
|
|
93
145
|
server.registerTool('theme_update', {
|
|
94
146
|
title: 'Proje temasını güncelle',
|
|
95
147
|
description: "Temayı KISMİ günceller: kaydı okur, verdiğiniz alanları derin birleştirir, normalize eder, lint'ten geçirir ve yazar — bir rengi değiştirmek preset'leri ve bileşen preset'lerini düşürmez. Yanıt hangi yolların değiştiğini (changed) ve hangi CSS değişkeninin hangi değerden hangi değere gittiğini (cssVarsChanged) söyler; dryRun ile yazmadan aynı yanıtı alırsınız. DİKKAT: tema PROJE kaydıdır — değişiklik BÜTÜN ortamların runtime'ına iner (production dâhil) ve Studio tema paneli açık ve kaydedilmemişse panelin sonraki Save'i bu yazmayı ezer.",
|
package/dist/tools/workflows.js
CHANGED
|
@@ -4,7 +4,7 @@ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/poli
|
|
|
4
4
|
import { workflowOutline, resolvePart, resolvePatchOperations } from '../knowledge/outlines.js';
|
|
5
5
|
import { patchResult, baseVersionOf } from '../platform/patch.js';
|
|
6
6
|
import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
|
|
7
|
-
import { policyRules, microfunctionStepFindings, undefinedInputFieldFindings } from '../knowledge/rules.js';
|
|
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
10
|
import fastJsonPatch from 'fast-json-patch';
|
|
@@ -119,6 +119,7 @@ export function registerWorkflowTools(server, platform) {
|
|
|
119
119
|
const findings = enforcePolicy([
|
|
120
120
|
...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'create', workflowKind: definition.type }, policyOverride),
|
|
121
121
|
...await microfunctionStepFindings(platform, project, environment, definition, definition.type, policyOverride),
|
|
122
|
+
...await workflowStepFindings(platform, project, environment, applicationId, definition, definition.type, policyOverride),
|
|
122
123
|
...await undefinedInputFieldFindings(platform, project, environment, definition, definition.type, policyOverride)
|
|
123
124
|
]);
|
|
124
125
|
const drift = unmappedMembers('CreateWorkflowCommand', definition);
|
|
@@ -135,6 +136,7 @@ export function registerWorkflowTools(server, platform) {
|
|
|
135
136
|
const findings = enforcePolicy([
|
|
136
137
|
...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'update', workflowKind: definition.type }, policyOverride),
|
|
137
138
|
...await microfunctionStepFindings(platform, project, environment, definition, definition.type, policyOverride),
|
|
139
|
+
...await workflowStepFindings(platform, project, environment, applicationId, definition, definition.type, policyOverride),
|
|
138
140
|
...await undefinedInputFieldFindings(platform, project, environment, definition, definition.type, policyOverride)
|
|
139
141
|
]);
|
|
140
142
|
const drift = unmappedMembers('UpdateWorkflowCommand', definition);
|
|
@@ -156,6 +158,7 @@ export function registerWorkflowTools(server, platform) {
|
|
|
156
158
|
const findings = enforcePolicy([
|
|
157
159
|
...evaluatePolicy(config, policyRules, 'workflow', patched, { operation: 'update', workflowKind: patched.type }, policyOverride),
|
|
158
160
|
...await microfunctionStepFindings(platform, project, environment, patched, patched.type, policyOverride),
|
|
161
|
+
...await workflowStepFindings(platform, project, environment, applicationId, patched, patched.type, policyOverride),
|
|
159
162
|
...await undefinedInputFieldFindings(platform, project, environment, patched, patched.type, policyOverride)
|
|
160
163
|
]);
|
|
161
164
|
const drift = introducedUnmappedMembers('UpdateWorkflowCommand', (current?.data ?? current), patched);
|
|
@@ -202,6 +205,7 @@ export function registerWorkflowTools(server, platform) {
|
|
|
202
205
|
const findings = enforcePolicy([
|
|
203
206
|
...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'preview', workflowKind: definition.type }, policyOverride),
|
|
204
207
|
...await microfunctionStepFindings(platform, project, environment, definition, definition.type, policyOverride),
|
|
208
|
+
...await workflowStepFindings(platform, project, environment, applicationId, definition, definition.type, policyOverride),
|
|
205
209
|
...await undefinedInputFieldFindings(platform, project, environment, definition, definition.type, policyOverride)
|
|
206
210
|
]);
|
|
207
211
|
const payload = await platform.tenant('POST', project, `/workflows/${applicationId}/preview`, { environment, body: definition, headers: languageHeader(language, normalized) });
|
|
@@ -209,7 +213,7 @@ export function registerWorkflowTools(server, platform) {
|
|
|
209
213
|
}));
|
|
210
214
|
server.registerTool('workflow_step_schema', {
|
|
211
215
|
title: 'Step şeması üret',
|
|
212
|
-
description:
|
|
216
|
+
description: "Verilen step tanımı için girdi/çıktı şemasını üretir (parameterScheme / returnScheme); step yazarken definition'ı doğrulamanın en hızlı yolu. type:'workflow' adımında (rule.workflowId) çağrılan akışın parametre ve dönüş DTO'sunu döner — alt akışa girdi YALNIZ rule.expression ile geçer, nesne literalinin anahtarları bu parameterScheme'in alanlarıdır (WF-032; platform_guide('workflow') §Workflow adımı).",
|
|
213
217
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, step: definitionParam },
|
|
214
218
|
annotations: readOnly
|
|
215
219
|
}, handle(config, ({ project, environment, applicationId, id, step }) => platform.tenant('POST', project, `/workflows/${applicationId}/${id}/steps/schema`, { environment, body: step })));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appaflytech/zeyrek-ai-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
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",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"harvest-examples": "tsx scripts/harvest-examples.ts",
|
|
36
36
|
"kabul:import-adres": "tsx scripts/import-kabul-adres.ts",
|
|
37
37
|
"kabul:import-senaryolar": "tsx scripts/import-kabul-senaryolar.ts",
|
|
38
|
+
"kabul:workflow-hata-zinciri": "tsx scripts/kabul-workflow-hata-zinciri.ts",
|
|
38
39
|
"benchmark:report": "tsx scripts/benchmark-report.ts"
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|