@appaflytech/zeyrek-ai-mcp 0.0.41 → 0.0.43
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/dist/knowledge/componentCatalog.js +27 -97
- package/dist/knowledge/componentEvents.js +1 -18
- package/dist/knowledge/mobileComponentCatalog.js +114 -15
- package/dist/knowledge/mobileComponentEvents.js +41 -0
- package/dist/knowledge/motionResolve.js +252 -0
- package/dist/knowledge/normalize.js +87 -59
- package/dist/knowledge/pageDiscover.js +2 -2
- package/dist/knowledge/platform/batchOps.js +70 -0
- package/dist/knowledge/platform/heroTag.js +153 -0
- package/dist/knowledge/platform/lint.js +128 -6
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
- package/dist/knowledge/platform/mobileRules.js +18 -0
- package/dist/knowledge/platform/pageTransition.js +57 -0
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +52 -1
- package/dist/knowledge/styleResolveNative.js +88 -4
- package/dist/platform/capture/actions.js +4 -1
- package/dist/platform/capture/capture.js +10 -13
- package/dist/platform/capture/persistent-browser.js +130 -0
- package/dist/resources/examples/flow-list-to-form.json +8 -10
- package/dist/resources/examples/flow-load-table.json +1 -1
- package/dist/resources/examples/mobile-list-detail-form.json +4 -3
- package/dist/resources/examples/mobile-scan-code.json +285 -0
- package/dist/resources/guides/component.md +3 -3
- package/dist/resources/guides/expressions.md +2 -2
- package/dist/resources/guides/merge.md +98 -18
- package/dist/resources/guides/microservice.md +2 -2
- package/dist/resources/guides/mobile-layout.md +2 -1
- package/dist/resources/guides/mobile-publish.md +1 -0
- package/dist/resources/guides/mobile-steps.md +67 -0
- package/dist/resources/guides/mobile.md +27 -55
- package/dist/resources/guides/page.md +182 -121
- package/dist/resources/guides/translation.md +26 -0
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
- package/dist/tools/activities.js +3 -1
- package/dist/tools/applications.js +16 -2
- package/dist/tools/components.js +12 -6
- package/dist/tools/designSchemaPatch.js +18 -13
- package/dist/tools/layouts.js +7 -2
- package/dist/tools/merges.js +29 -9
- package/dist/tools/pages.js +129 -127
- package/dist/tools/platformInfo.js +3 -2
- package/dist/tools/profileWrite.js +2 -2
- package/dist/tools/schemaOps.js +132 -34
- package/dist/tools/screenWrite.js +1 -1
- package/dist/tools/screens.js +3 -3
- package/package.json +2 -2
package/dist/tools/components.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
-
import { normalizeComponentDefaults, componentDefaultsNote, assertComponentBlocks, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, normalizeDroppableStates, withNormalization } from '../knowledge/normalize.js';
|
|
2
|
+
import { normalizeComponentDefaults, assertNoRetiredNodes, componentDefaultsNote, assertComponentBlocks, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, normalizeDroppableStates, withNormalization } from '../knowledge/normalize.js';
|
|
3
3
|
import { eventContractsFor, eventPayloadNote } from '../knowledge/componentEvents.js';
|
|
4
4
|
import { componentPresetDefaults, readProjectTheme } from '../knowledge/themeSchema.js';
|
|
5
|
-
import { knowledgeFor, knowledgeNote, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
|
|
5
|
+
import { knowledgeFor, knowledgeNote, retiredComponent, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
|
|
6
6
|
import { MOBILE_STATE_FLAG_NOTES, mobileComponentKnowledge } from '../knowledge/mobileComponentCatalog.js';
|
|
7
7
|
import { mobileComponentEventContracts } from '../knowledge/mobileComponentEvents.js';
|
|
8
8
|
import { componentUpdateBody, fetchHost, schemaParam, unwrap } from './screens.js';
|
|
@@ -184,7 +184,7 @@ export function registerComponentTools(server, platform) {
|
|
|
184
184
|
const config = platform.config;
|
|
185
185
|
server.registerTool('component_list', {
|
|
186
186
|
title: 'Komponent sözlüğünü listele',
|
|
187
|
-
description: "Sayfaya konabilecek kayıtlı komponent tiplerini (slug, name, kategori, droppable) kompakt listeler; tek tipin tam sözlüğü + davranış bilgisi için component_get, komponent modelinin geneli için platform_guide('component'). scope:'all' platformun varsayılan kataloğunu da getirir.",
|
|
187
|
+
description: "Sayfaya konabilecek kayıtlı komponent tiplerini (slug, name, kategori, droppable) kompakt listeler; retired: true taşıyan tip katalogdan kaldırılmıştır, yerine replacedBy kullanılır; tek tipin tam sözlüğü + davranış bilgisi için component_get, komponent modelinin geneli için platform_guide('component'). scope:'all' platformun varsayılan kataloğunu da getirir.",
|
|
188
188
|
inputSchema: {
|
|
189
189
|
project: projectParam,
|
|
190
190
|
environment: environmentParam,
|
|
@@ -207,13 +207,16 @@ export function registerComponentTools(server, platform) {
|
|
|
207
207
|
categories: component.categories,
|
|
208
208
|
isDroppable: component.isDroppable,
|
|
209
209
|
kind: component.kind,
|
|
210
|
-
widgetType: component.widgetType
|
|
210
|
+
widgetType: component.widgetType,
|
|
211
|
+
...(retiredComponent(typeof component.slug === 'string' ? component.slug : undefined)
|
|
212
|
+
? { retired: true, replacedBy: retiredComponent(String(component.slug)).replacedBy }
|
|
213
|
+
: {})
|
|
211
214
|
}))
|
|
212
215
|
};
|
|
213
216
|
}));
|
|
214
217
|
server.registerTool('component_get', {
|
|
215
218
|
title: 'Komponent sözlüğünü getir',
|
|
216
|
-
description: "Tek komponent tipinin tam sözlüğü: prop'lar (tip/enum/default), event'ler (yükleriyle), davranış bilgisi (knowledge) ve boyut sınırları. Şemaya uydurma prop yazmayın — yalnız burada dönenleri kullanın. Yanıtta guide alanı dönerse derin rehberi platform_guide ile okuyun (örn. html). slug ya da id ile çağrılır.",
|
|
219
|
+
description: "Tek komponent tipinin tam sözlüğü: prop'lar (tip/enum/default), event'ler (yükleriyle), davranış bilgisi (knowledge) ve boyut sınırları. Şemaya uydurma prop yazmayın — yalnız burada dönenleri kullanın. Yanıtta guide alanı dönerse derin rehberi platform_guide ile okuyun (örn. html). Yanıtta retired alanı dönerse tip katalogdan kaldırılmıştır (kayıt defterinde eşitleme silmediği için durur): o tipi yeni düğümde kullanmayın, replacedBy'ı kullanın. slug ya da id ile çağrılır.",
|
|
217
220
|
inputSchema: {
|
|
218
221
|
project: projectParam,
|
|
219
222
|
environment: environmentParam,
|
|
@@ -242,6 +245,7 @@ export function registerComponentTools(server, platform) {
|
|
|
242
245
|
: [];
|
|
243
246
|
const componentSlug = typeof component.slug === 'string' ? component.slug : undefined;
|
|
244
247
|
const knowledge = knowledgeFor(componentSlug);
|
|
248
|
+
const retired = retiredComponent(componentSlug);
|
|
245
249
|
// Mobil uygulamada kayıt mobil kataloğun satırıdır (`listComponents` type=mobile); davranış
|
|
246
250
|
// notları web runtime'ından gelir (`knowledge` — mobil şema web sınıfını genişletir), bileşenin
|
|
247
251
|
// mobil farkları `mobileKnowledge`'ta, bütün mobil bileşenlere geçen notlar `mobileNotes`'ta (G4).
|
|
@@ -262,6 +266,7 @@ export function registerComponentTools(server, platform) {
|
|
|
262
266
|
slug: component.slug,
|
|
263
267
|
name: component.name,
|
|
264
268
|
description: component.description,
|
|
269
|
+
...(retired ? { retired } : {}),
|
|
265
270
|
kind: component.kind,
|
|
266
271
|
widgetType: component.widgetType,
|
|
267
272
|
isDroppable: component.isDroppable,
|
|
@@ -282,7 +287,7 @@ export function registerComponentTools(server, platform) {
|
|
|
282
287
|
...(componentSlug && INTERNAL_ROLES_BY_TYPE[componentSlug]
|
|
283
288
|
? {
|
|
284
289
|
internalRoles: INTERNAL_ROLES_BY_TYPE[componentSlug],
|
|
285
|
-
internalRolesNote: "Bu komponentin İÇ kontrolleri dışarıdan adreslenebilir: page_screenshot actions hedefine '<elementId>__<rol>' yazın (ör. '
|
|
290
|
+
internalRolesNote: "Bu komponentin İÇ kontrolleri dışarıdan adreslenebilir: page_screenshot actions hedefine '<elementId>__<rol>' yazın (ör. 'tabs_ayar__tab-genel'). Kimlikler runtime'ın data-element-id özniteliğidir; ikinci bir mekanizma yoktur."
|
|
286
291
|
}
|
|
287
292
|
: {}),
|
|
288
293
|
...(themeDefaults ? { themeDefaults } : {}),
|
|
@@ -322,6 +327,7 @@ export function registerComponentTools(server, platform) {
|
|
|
322
327
|
if (body.kind === 'widget' && !body.widgetType) {
|
|
323
328
|
throw new PlatformApiError("kind:'widget' için widgetType (container | modal | drawer) zorunludur.");
|
|
324
329
|
}
|
|
330
|
+
assertNoRetiredNodes(body.schema);
|
|
325
331
|
const wrapped = body.kind === 'widget' && body.widgetType && (body.schema ?? []).length > 0
|
|
326
332
|
? wrapWidgetRoot(body.schema ?? [], body.widgetType, await listComponents(platform, project, environment, applicationId).catch(() => []))
|
|
327
333
|
: { schema: body.schema, notes: [] };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, normalizeSchemaOps, assertAddComponentIds, validationNote, componentNameNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
|
|
2
|
-
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes,
|
|
1
|
+
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, normalizeSchemaOps, assertAddComponentIds, assertNoRetiredTypes, validationNote, componentNameNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
|
|
2
|
+
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, removeNodeFromTree, planEventRemovals, callerSlots, planEmulatedOps, recordsWithEventRemovals, serverOpsInOrder, assertOpBodies, applySkeletonOps, reshapesSkeleton } from './schemaOps.js';
|
|
3
3
|
import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
|
|
4
4
|
import { fetchHost, hostPath } from './screens.js';
|
|
5
5
|
import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
|
|
@@ -10,6 +10,7 @@ import { PlatformApiError } from '../platform/errors.js';
|
|
|
10
10
|
import { connectDataOpIssues, connectedPropKeys } from '../knowledge/connectDataDefinition.js';
|
|
11
11
|
import { handle } from '../platform/responses.js';
|
|
12
12
|
import { slotWarning } from './layouts.js';
|
|
13
|
+
import { batchEnvelopeNote, openBatchEnvelopes } from '../knowledge/platform/batchOps.js';
|
|
13
14
|
import { applicationTypeField, resolveProfile } from '../knowledge/platform/resolve.js';
|
|
14
15
|
import { boundFlowSteps, layoutPatchWarnings, screenProfileWarnings } from './profileWrite.js';
|
|
15
16
|
import { z } from 'zod';
|
|
@@ -57,12 +58,14 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
57
58
|
},
|
|
58
59
|
annotations: updates
|
|
59
60
|
}, handle(config, async ({ project, environment, applicationId, id, ops, operations, allowFlowDeletion, view }) => {
|
|
60
|
-
const
|
|
61
|
+
const opened = openBatchEnvelopes(spec.tool, microOps(spec.tool, ops, operations).map(op => op));
|
|
62
|
+
const allOps = opened.ops;
|
|
61
63
|
const removeEventOps = allOps.filter(entry => entry.op === 'remove_event');
|
|
62
64
|
const resizeOps = allOps.filter(entry => entry.op === 'resize_component');
|
|
63
65
|
const propTypeOps = allOps.filter(entry => entry.op === 'set_prop_type');
|
|
64
66
|
const opRecords = allOps.filter(entry => !['remove_event', 'resize_component', 'set_prop_type'].includes(String(entry.op)));
|
|
65
67
|
assertAddComponentIds(opRecords);
|
|
68
|
+
assertNoRetiredTypes(opRecords);
|
|
66
69
|
const record = await fetchHost(platform, host, project, environment, applicationId, id);
|
|
67
70
|
// Platform ucu kaydın türüne bakmaz: sıradan bir komponentin id'si verilirse kataloğundaki
|
|
68
71
|
// tanımın yanına bir şema yazılırdı.
|
|
@@ -78,15 +81,13 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
78
81
|
const sectionToggles = expandSectionToggles(opRecords, nodes, sectionIds);
|
|
79
82
|
const sectionNotes = boxSectionNote([...createdSections, ...sectionToggles.created], sectionToggles.removed);
|
|
80
83
|
const events = host === 'layout' ? record.events : undefined;
|
|
81
|
-
|
|
82
|
-
// (TB-129, table.style.th/td kebab-case kalır) ve tip yalnız okunan ağaçta var. Gövde hataları
|
|
83
|
-
// bu yüzden bir okuma sonrasında rapor edilir; karar hiç değişmez, yalnız sıra değişti.
|
|
84
|
-
const normalized = normalizeSchemaOps(opRecords, styleTargetTypesFrom(opRecords, nodes));
|
|
84
|
+
const normalized = normalizeSchemaOps(opRecords);
|
|
85
85
|
assertOpBodies(opRecords, propTypeOps);
|
|
86
86
|
// CD-005 layout ve widget host'unda da: bağlı prop'u (iç içe yol dahil) ezen yazma ve bağlı olmayan prop'ta
|
|
87
87
|
// remove_connect_data okunan ağaca bakarak reddedilir (gövde denetimi assertOpBodies'te koştu).
|
|
88
88
|
const connectedProps = connectedPropKeys(nodes);
|
|
89
|
-
|
|
89
|
+
// `set_prop_type` ayrı listededir; bağlı prop'u ezen yazımı o da yapar (sayfa yamasıyla aynı kapı).
|
|
90
|
+
const connectionIssues = [...opRecords, ...propTypeOps].flatMap(entry => connectDataOpIssues(entry, connectedProps).map(issue => `${issue.code} — ${issue.message}`));
|
|
90
91
|
if (connectionIssues.length > 0)
|
|
91
92
|
throw new PlatformApiError(`Connect Data op gövdesi geçersiz (istek gönderilmedi): ${connectionIssues.join(' · ')}`, 400);
|
|
92
93
|
if (opRecords.some(entry => entry.op === 'set_event' || entry.op === 'add_event')) {
|
|
@@ -102,7 +103,9 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
102
103
|
throw new PlatformApiError(`PAGE-008 — geçersiz olay adı (istek gönderilmedi): ${unknown.join(' · ')}`, 400);
|
|
103
104
|
}
|
|
104
105
|
stripGatewayEventFields(opRecords);
|
|
105
|
-
|
|
106
|
+
// Bölüm anahtarı op'larını yukarıda ekledi; buradan sonra `opRecords` yeni op almaz.
|
|
107
|
+
const slots = callerSlots(allOps, opRecords);
|
|
108
|
+
const deletedFlows = orphanedFlowPlan(nodes, events, recordsWithEventRemovals(slots, opRecords));
|
|
106
109
|
if (deletedFlows.length > 0 && allowFlowDeletion !== true)
|
|
107
110
|
throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
|
|
108
111
|
const added = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component);
|
|
@@ -120,7 +123,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
120
123
|
// her yamada koşsaydı kayıtlı ağacında zaten iç içe repeater olan layout hiç yamalanamazdı.
|
|
121
124
|
const simulatable = opRecords.some(entry => ['add_component', 'move_component'].includes(String(entry.op))
|
|
122
125
|
|| (entry.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(entry.prop ?? ''))));
|
|
123
|
-
const nested = simulatable ? nestedRepeaterNodes(simulated) : [];
|
|
126
|
+
const nested = simulatable ? nestedRepeaterNodes(simulated, resolved.profile.platform) : [];
|
|
124
127
|
if (nested.length > 0)
|
|
125
128
|
throw new PlatformApiError(nestedRepeaterMessage(nested, resolved.profile.platform), 400);
|
|
126
129
|
const reshaped = simulatable || opRecords.some(entry => entry.op === 'remove_component');
|
|
@@ -145,9 +148,10 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
145
148
|
layoutWarnings.push(...layoutPatchWarnings(resolved, nodes, skeleton));
|
|
146
149
|
}
|
|
147
150
|
const removedEvents = planEventRemovals(removeEventOps);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
+
// Sayfa yamasıyla aynı iki söz (`pages.ts` `planPageWrite`, PAGE-050): öykünülen op ağacın o andaki hâline göre
|
|
152
|
+
// planlanır ve liste platforma çağıranın sırasıyla gider.
|
|
153
|
+
const { resized, propTypes } = planEmulatedOps(nodes, slots, opRecords, spec.readTool);
|
|
154
|
+
const serverOps = serverOpsInOrder(slots, { sent: opRecords, resized: resized.groups, propTypes: propTypes.serverOps });
|
|
151
155
|
const response = ((await platform.tenant('PATCH', project, `${hostPath(host, applicationId, id)}/schema`, {
|
|
152
156
|
environment,
|
|
153
157
|
body: { ops: serverOps }
|
|
@@ -181,6 +185,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
return withWriteDiff(withNormalization(response, [
|
|
188
|
+
...batchEnvelopeNote(opened),
|
|
184
189
|
...componentNameNote(normalized.names),
|
|
185
190
|
...sectionNotes,
|
|
186
191
|
...componentDefaultsNote(normalized.components),
|
package/dist/tools/layouts.js
CHANGED
|
@@ -2,7 +2,7 @@ import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, u
|
|
|
2
2
|
import { resolvePatchOperations } from '../knowledge/outlines.js';
|
|
3
3
|
import { patchResult, baseVersionOf } from '../platform/patch.js';
|
|
4
4
|
import fastJsonPatch from 'fast-json-patch';
|
|
5
|
-
import { fillComponentNames, componentNameNote, normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
5
|
+
import { fillComponentNames, componentNameNote, normalizeComponentDefaults, assertNoRetiredNodes, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
6
6
|
import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode, readFullSchema } from './screens.js';
|
|
7
7
|
import { assertScreenWriteGuards, assertScreenCatalog } from './screenWrite.js';
|
|
8
8
|
import { applyPropTypesToSchema, isSetPropTypeOp, setPropTypeErrors } from '../knowledge/propTypes.js';
|
|
@@ -15,7 +15,7 @@ import { jsonArray } from '../platform/jsonArray.js';
|
|
|
15
15
|
import { z } from 'zod';
|
|
16
16
|
const patchOperationsParam = jsonArray(z.array(z.record(z.unknown())).min(1))
|
|
17
17
|
.describe("JSON Patch listesi (RFC 6902): { op, path, value }. Yol seçici alabilir — '/views/0/children[elementId=baslik]/props/content/data'.");
|
|
18
|
-
const viewsParam = schemaParam.describe("Layout'un komponent ağacı: her düğümde elementId, name (= elementId), type, blocks zorunlu — blocks: [{ breakpoint: 1024, size: { w, h }, position: { x, y } }], gateway doldurmaz (PAGE-037). DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir; ağaçta hedefli değişiklik için tam ağaç yerine layout_schema_patch micro-op'larını kullanın. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür. 2026-09-08'den beri views, page_schema_patch'in doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile REDDEDİLİR (kabul edilen değer listesiyle) ve hiçbir şey kaydedilmez — eskiden sessizce kaydediliyordu (P1-2).");
|
|
18
|
+
const viewsParam = schemaParam.describe("Layout'un komponent ağacı: her düğümde elementId, name (= elementId), type, blocks zorunlu — blocks: [{ breakpoint: 1024, size: { w, h }, position: { x, y } }], gateway doldurmaz (PAGE-037). DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir; ağaçta hedefli değişiklik için tam ağaç yerine layout_schema_patch micro-op'larını kullanın. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür. 2026-09-08'den beri views, page_schema_patch'in doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile REDDEDİLİR (kabul edilen değer listesiyle) ve hiçbir şey kaydedilmez — eskiden sessizce kaydediliyordu (P1-2). layout_create'te katalogdan kaldırılan tipte düğüm (component_list'te retired: true) PAGE-051 ile reddedilir.");
|
|
19
19
|
function countSlots(nodes) {
|
|
20
20
|
if (!Array.isArray(nodes))
|
|
21
21
|
return 0;
|
|
@@ -118,6 +118,7 @@ export function registerLayoutTools(server, platform) {
|
|
|
118
118
|
const resolved = await resolveProfile(platform, applicationId);
|
|
119
119
|
const named = fillComponentNames(views);
|
|
120
120
|
assertScreenWriteGuards('layout', { schema: views, events }, resolved.profile.platform);
|
|
121
|
+
assertNoRetiredNodes(views);
|
|
121
122
|
const fixed = normalizeComponentDefaults(views);
|
|
122
123
|
const catalogNotes = await assertScreenCatalog(platform, project, environment, applicationId, views, 'layout_create');
|
|
123
124
|
const gaps = [
|
|
@@ -227,6 +228,10 @@ export function registerLayoutTools(server, platform) {
|
|
|
227
228
|
const profile = await resolveProfile(platform, applicationId);
|
|
228
229
|
const named = fillComponentNames(overrides.views);
|
|
229
230
|
assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events }, profile.profile.platform);
|
|
231
|
+
// PAGE-051: ağacın tamamı değil yalnız yamanın EKLEDİĞİ düğümler — kayıtlı emekli düğüm yazılabilir kalır.
|
|
232
|
+
assertNoRetiredNodes(resolved.operations
|
|
233
|
+
.filter(op => op.op === 'add' && /\/(views|children)\/(\d+|-)$/.test(String(op.path ?? '')))
|
|
234
|
+
.map(op => op.value));
|
|
230
235
|
const fixed = normalizeComponentDefaults(overrides.views);
|
|
231
236
|
const catalogNotes = await assertScreenCatalog(platform, project, environment, applicationId, overrides.views, 'layout_patch');
|
|
232
237
|
const body = layoutUpdateBody({ ...current, applicationId }, overrides);
|
package/dist/tools/merges.js
CHANGED
|
@@ -11,8 +11,14 @@ const mergeDescription = z.string().trim().max(2000).optional()
|
|
|
11
11
|
.describe("Dalda ne değişti ve neden merge edilmeli — gözden geçirene yazılan gövde. Merge bunu ASLA ezmez; opsiyoneldir ama açıklamasız istekte değişiklik listesi okunacak tek şey kalır.");
|
|
12
12
|
const resolution = z.object({
|
|
13
13
|
itemKey: z.string().min(1).describe("Plan kaydının anahtarı, ör. 'page:8f1c…' (merge_get çıktısındaki items[].key)"),
|
|
14
|
-
path: z.string().optional().describe("Eleman yolu, ör. 'components[elementId=btn_save].props[name=label].ref'; boş bırakılırsa o kaydın TÜM çakışmaları çözülür"),
|
|
15
|
-
choice: z.enum(['source', 'target']).describe("source = dal, target = development")
|
|
14
|
+
path: z.string().optional().describe("Eleman yolu, ör. 'components[elementId=btn_save].props[name=label].ref'; boş bırakılırsa o kaydın TÜM çakışmaları çözülür (custom'da yalnız kaydın tek çakışması varsa)"),
|
|
15
|
+
choice: z.enum(['source', 'target', 'both', 'custom']).describe("source = dal, target = development, both = iki tarafın birleşimi — yalnız combinable: true elemanda, birleşimi platform hesaplar; duplicate'te ikisini de tut demektir (slug taşıyan kopyada kapalı, combinable: false; base dolu kopyada source da kapalı). custom = value'daki değer"),
|
|
16
|
+
value: z.unknown().optional().describe("YALNIZ choice='custom' ile ve orada ZORUNLU: yazılacak değer. Türü (dizi/nesne/skaler) source ya da target ile aynı olmalı. Kök yolda kaydın gövdesidir, order-conflict'te kimlik listesidir, duplicate'te tek öğedir (kimliği development'ınki olur), tipi değişen prop'ta ('…props[id=…]') prop gövdesidir — valueType ile ref birlikte yazılır, kimliği yoldan gelir, statik listenin satırlarını (children) sunucu ref'ten kurar.")
|
|
17
|
+
}).superRefine((entry, context) => {
|
|
18
|
+
if (entry.choice === 'custom' && entry.value === undefined)
|
|
19
|
+
context.addIssue({ code: 'custom', path: ['value'], message: "choice='custom' değer ister: yazılacak değeri value alanında gönderin" });
|
|
20
|
+
if (entry.choice !== 'custom' && entry.value !== undefined)
|
|
21
|
+
context.addIssue({ code: 'custom', path: ['value'], message: "value yalnız choice='custom' ile gönderilir; source / target / both değer taşımaz" });
|
|
16
22
|
});
|
|
17
23
|
/**
|
|
18
24
|
* B-08 (2026-09-09 koşusu): `ResolveMergeConflictsCommandHandler` yanıtı
|
|
@@ -30,7 +36,7 @@ export function resolveSummary(payload, planId, requested) {
|
|
|
30
36
|
...(plan.status === undefined ? {} : { status: plan.status }),
|
|
31
37
|
...(plan.summary === undefined ? {} : { summary: plan.summary }),
|
|
32
38
|
resolved: { requested, elements: resolvedElements },
|
|
33
|
-
note: "Yanıt ÖZETTİR — plan gövdesi kesildi. Planın kalan çakışmaları için merge_get { projectId, planId, view: 'conflicts' }; summary.unresolved 0 olduğunda merge_apply."
|
|
39
|
+
note: "Yanıt ÖZETTİR — plan gövdesi kesildi. Planın kalan çakışmaları için merge_get { projectId, planId, view: 'conflicts' }; summary.unresolved 0 olduğunda merge_apply. summary.duplicates çözümsüz olası kopyalardır: combinable: true olan kopya merge'ü engellemez (ikisi de yazılır), slug taşıyan kopya (combinable: false) ise summary.unresolved'a sayılır ve birini seçene kadar apply edilmez; merge_get { checks: true } okunmadan apply edilmez."
|
|
34
40
|
};
|
|
35
41
|
}
|
|
36
42
|
export function registerMergeTools(server, platform) {
|
|
@@ -55,13 +61,27 @@ export function registerMergeTools(server, platform) {
|
|
|
55
61
|
}, handle(config, ({ projectId, ...query }) => platform.org('GET', `/merges/${projectId}`, { query })));
|
|
56
62
|
server.registerTool('merge_get', {
|
|
57
63
|
title: 'Merge planını oku',
|
|
58
|
-
description: "Bir merge planını döner. items[] yalnız DEĞİŞEN kayıtları taşır; her kayıtta kind (add|update|delete|conflict) ve elements[] vardır. Çakışan elemanlarda base/source/target üç değeri birden gelir. dataRisk: true olan kalemde SEBEP dataRiskReasons[] içindedir: 'field-removed' (alan siliniyor), 'field-narrowed' (tip/nullable/unique daraltılıyor), 'not-null-without-default-on-populated-table' (hedefte satır varken varsayılansız NOT NULL alan ekleniyor — bu apply'ı DDL aşamasında düşürür, alanı nullable yapın ya da varsayılan verin), 'table-removed' (hedefte satırı olan tablo siliniyor). dataRisk bir UYARIDIR, apply'ı bloklamaz. items[].dependents[] ({ id, name, module }) dolu olan kalem, DALIN SİLDİĞİ ama development'ın dal açıldıktan sonra kullanmaya başladığı kayıttır: silme ÇAKIŞMAYA döner; hedef lehine çözüm kaydı ve bağımlısını korur, kaynak lehine çözüm siler (bağımlı FK ile düşebilir). Sorgunun çıktı DTO'su ve alanları aynı çakışma grubuna girer. Süreç kenarlarında (edges) iki taraf AYNI geçidin çıkışlarına dokunduysa yol 'versions[id=…].model.edges{from=<düğüm>}' olan TEK çakışma gelir; farklı geçitler kendiliğinden birleşir. Merge çevirileri (translation, adı 'key') ve microfunction'ları da taşır; microfunction hedefte TASLAK kalır, yayını kullanıcı yapar. derivedFromKey dolu olan kalem OTOMATİK ÜRETİLMİŞ bir kayıttır (varsayılan DTO); kaynağını (entity ya da query) çözdüğünüzde aynı yoldaki çakışması aynı tarafa KENDİLİĞİNDEN çözülür — ayrı ayrı ve zıt çözülemez. Uygulanmış planlarda title, description, createdBy ve appliedBy da gelir. kind=revert ise bu bir geri alma planıdır: revertsPlanId geri aldığı planı verir ve elements'te source merge ÖNCESİ, target ŞİMDİKİ değerdir. isStale: true ise plan alındığından beri DAL ya da development değişmiştir: items o değişikliği taşimaz ve merge_apply 409 döner — merge_refresh ile planı tazeleyin. elements[].isNoise: true olan eleman GÖSTERİM gürültüsüdür — kanvasın kendi durumu (workflow schema), DTO'dan türetilen şema (microservice schema.parameterScheme/returnScheme), sunucu yönetimli anahtar; merge gövdesinde KALIR ve uygulanır, yalnız okurken atlanır. Çakışan eleman asla işaretlenmez. items[].references[] o kalemin gövdesindeki guid'lerin karşılığıdır ({ id, name, module }): bir table step'in rule.entityId'si hangi tabloya, bir branch'in nextStepId'si hangi adıma, bir filtrenin fieldId'si hangi alana işaret ediyor — plan dışına çıkmadan okunur. items[].parentKey dolu olan kayıt başka bir kaydın çocuğudur (DTO çocukları, iç içe klasörler); ebeveyniyle birlikte okunur. items[].recordType kaydın modül içindeki türüdür — bir workflow kaydı 'workflow', 'automation' ya da 'middleware' olabilir ve modül üçüne de workflow der. items[].hiddenFields o kaydın GÖVDESİNDE okunmayacak anahtarlardır (aynı politika; eklenen/silinen kayıt tek eleman olarak geldiği için yolun değil anahtarın sorusudur).",
|
|
59
|
-
inputSchema: {
|
|
64
|
+
description: "Bir merge planını döner. items[] yalnız DEĞİŞEN kayıtları taşır; her kayıtta kind (add|update|delete|conflict) ve elements[] vardır. Çakışan elemanlarda base/source/target üç değeri birden gelir. dataRisk: true olan kalemde SEBEP dataRiskReasons[] içindedir: 'field-removed' (alan siliniyor), 'field-narrowed' (tip/nullable/unique daraltılıyor), 'not-null-without-default-on-populated-table' (hedefte satır varken varsayılansız NOT NULL alan ekleniyor — bu apply'ı DDL aşamasında düşürür, alanı nullable yapın ya da varsayılan verin), 'table-removed' (hedefte satırı olan tablo siliniyor). dataRisk bir UYARIDIR, apply'ı bloklamaz. items[].dependents[] ({ id, name, module }) dolu olan kalem, DALIN SİLDİĞİ ama development'ın dal açıldıktan sonra kullanmaya başladığı kayıttır: silme ÇAKIŞMAYA döner; hedef lehine çözüm kaydı ve bağımlısını korur, kaynak lehine çözüm siler (bağımlı FK ile düşebilir). Sorgunun çıktı DTO'su ve alanları aynı çakışma grubuna girer. Süreç kenarlarında (edges) iki taraf AYNI geçidin çıkışlarına dokunduysa yol 'versions[id=…].model.edges{from=<düğüm>}' olan TEK çakışma gelir; farklı geçitler kendiliğinden birleşir. Merge çevirileri (translation, adı 'key') ve microfunction'ları da taşır; microfunction hedefte TASLAK kalır, yayını kullanıcı yapar. derivedFromKey dolu olan kalem OTOMATİK ÜRETİLMİŞ bir kayıttır (varsayılan DTO); kaynağını (entity ya da query) çözdüğünüzde aynı yoldaki çakışması aynı tarafa KENDİLİĞİNDEN çözülür — ayrı ayrı ve zıt çözülemez. Uygulanmış planlarda title, description, createdBy ve appliedBy da gelir. kind=revert ise bu bir geri alma planıdır: revertsPlanId geri aldığı planı verir ve elements'te source merge ÖNCESİ, target ŞİMDİKİ değerdir. isStale: true ise plan alındığından beri DAL ya da development değişmiştir: items o değişikliği taşimaz ve merge_apply 409 döner — merge_refresh ile planı tazeleyin. elements[].isNoise: true olan eleman GÖSTERİM gürültüsüdür — kanvasın kendi durumu (workflow schema), DTO'dan türetilen şema (microservice schema.parameterScheme/returnScheme), sunucu yönetimli anahtar; merge gövdesinde KALIR ve uygulanır, yalnız okurken atlanır. Çakışan eleman asla işaretlenmez. items[].references[] o kalemin gövdesindeki guid'lerin karşılığıdır ({ id, name, module }): bir table step'in rule.entityId'si hangi tabloya, bir branch'in nextStepId'si hangi adıma, bir filtrenin fieldId'si hangi alana işaret ediyor — plan dışına çıkmadan okunur. items[].parentKey dolu olan kayıt başka bir kaydın çocuğudur (DTO çocukları, iç içe klasörler); ebeveyniyle birlikte okunur. items[].recordType kaydın modül içindeki türüdür — bir workflow kaydı 'workflow', 'automation' ya da 'middleware' olabilir ve modül üçüne de workflow der. items[].hiddenFields o kaydın GÖVDESİNDE okunmayacak anahtarlardır (aynı politika; eklenen/silinen kayıt tek eleman olarak geldiği için yolun değil anahtarın sorusudur). Bir taraf prop'un TİPİNİ (valueType: static → code) değiştirip öbür taraf da tipine ya da değerine (ref/data) dokunduysa eleman prop'un KENDİSİDİR: yol '…props[id=…]', base/source/target prop gövdesi, combinable: false — tip ile değer tek karardır. Nesne prop'unda (icon, surfaceStyle) anahtarın değeri '…ref.<anahtar>…', çizilen satırı '…children[name=<anahtar>]…' yolundadır; ikisi de çakışırsa kararı ref'e verin — satır karar istemez, unresolved'a sayılmaz; merge onu değerin birleşik hâlinden kurar. ÇÖZÜM DEĞER TAŞIR: elements[].combinable: true olan çakışma (kimliksiz ya da gruplu liste) both ile birleştirilebilir; elements[].value both/custom çözümünün yazılacak değeridir. kind='duplicate' OLASI KOPYADIR: iki taraf aynı içeriği farklı kimlikle eklemiş, twinPath development'taki ikizdir. combinable: true olan kopya ENGELLEMEZ — çözümsüzse ikisi de yazılır; kopyalar aynı slug'ı taşıyorsa (tablo alanı = kolon, sayfa = rota) combinable: false gelir, ikisi birden kalamaz: unresolved sayılır, both reddedilir, birini seçmek gerekir. Tablo alanında slug, bileşen prop'unda ad TEKİLDİR: içerik farklı olsa da aynı slug'lı alan ya da aynı adlı prop eklemesi combinable: false kopyadır; aynı ad farklı slug'lı iki alan ayrı eklenir (name-clash uyarısı). Development tabanda var olan bir alanı dalın eklediği slug'a yeniden adlandırdıysa dalın alanı yine duplicate gelir; base doludur (alanın taban hâli) ve source 400 döner — veri taşıyan alan silinmez, target ya da custom seçilir. target dalınkini düşürür, source development'ınkini çıkarır, custom tek öğe yazar; düşen kopyadan türemiş DTO alanı da düşer. summary.duplicates çözümsüz kopya sayısıdır. items[].derivedFromPath dolu olan DTO alanı tablo alanına bağlıdır (fields[id=…]); tablo alanı kararı ona kendiliğinden geçer. checks: true merge öncesi doğrulamayı da ekler: unresolved (error) · duplicate-open · name-clash (aynı adla iki kardeş) · dangling-reference (merge'ün düşürdüğü kimliğe kalan başvuru) — uyarılar engellemez, merge_apply'dan önce okunur.",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
projectId: guid,
|
|
67
|
+
planId: guid,
|
|
68
|
+
view: mergeView,
|
|
69
|
+
checks: z.boolean().optional().describe("true ise planın merge öncesi doğrulaması (checks[]) yanıta eklenir; plan planned değilse boş gelir")
|
|
70
|
+
},
|
|
60
71
|
annotations: readOnly
|
|
61
|
-
}, handle(config, ({ projectId, planId, view }) =>
|
|
72
|
+
}, handle(config, async ({ projectId, planId, view, checks }) => {
|
|
73
|
+
const payload = await platform.org('GET', `/merges/${projectId}/${planId}?view=${view ?? 'conflicts'}`);
|
|
74
|
+
if (!checks)
|
|
75
|
+
return payload;
|
|
76
|
+
const checkPayload = await platform.org('GET', `/merges/${projectId}/${planId}/checks`);
|
|
77
|
+
return {
|
|
78
|
+
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
79
|
+
checks: checkPayload?.data?.checks ?? []
|
|
80
|
+
};
|
|
81
|
+
}));
|
|
62
82
|
server.registerTool('merge_resolve', {
|
|
63
83
|
title: 'Merge çakışmalarını çöz',
|
|
64
|
-
description: "Çakışan elemanlar için dal (
|
|
84
|
+
description: "Çakışan elemanlar ve olası kopyalar için kararı plana yazar: source (dal), target (development), both (iki tarafın birleşimi — yalnız combinable: true elemanda; kopyada ikisini de tut) ya da custom (value'daki değer). Yalnız status=planned iken çalışır. path boş bırakılırsa o kaydın tüm çakışmaları aynı karara çözülür. Türetilmiş kaydı (varsayılan DTO) kaynağına zıt çözmek 400 döner. YANIT ÖZETTİR: { planId, status, summary, resolved } — platform planın tamamını döner ama gateway onu kesip yalnız sayaçları verir (orta boy planda tam gövde 68-121 KB'a çıkıyor ve yanıt sınırını aşıyordu; çağrı hata gibi görünürken çözüm UYGULANMIŞ oluyordu). Planın kendisini görmek için merge_get { planId, view:'conflicts' } çağırın; kalan çakışma sayısı summary.unresolved'dadır.",
|
|
65
85
|
inputSchema: { projectId: guid, planId: guid, resolutions: z.array(resolution).min(1) },
|
|
66
86
|
annotations: updates
|
|
67
87
|
}, handle(config, async ({ projectId, planId, resolutions }) => {
|
|
@@ -82,14 +102,14 @@ export function registerMergeTools(server, platform) {
|
|
|
82
102
|
}));
|
|
83
103
|
server.registerTool('merge_refresh', {
|
|
84
104
|
title: 'Merge planını tazele',
|
|
85
|
-
description: "Açık bir planı dalın ve development'ın ŞU ANKİ hâline göre yeniden hesaplatır. İstek yerinde kalır: id'si, başlığı, açıklaması ve geçmişi korunur — yalnız değişiklik listesi yenilenir.
|
|
105
|
+
description: "Açık bir planı dalın ve development'ın ŞU ANKİ hâline göre yeniden hesaplatır. İstek yerinde kalır: id'si, başlığı, açıklaması ve geçmişi korunur — yalnız değişiklik listesi yenilenir. DEĞİŞMEYEN ELEMANIN KARARI KORUNUR: aynı kalem, aynı yol ve üç tarafı (base/source/target) aynı kalan elemanın kararı ve değeri yeni plana taşınır; değişenler yeniden sorulur (etkinlikte merge.resolutions_carried { carried, dropped }). Yalnız status=planned bir planda çalışır ve dal ready olmalıdır. Bayat bir planı (merge_get → isStale: true, ya da merge_apply 409) düzeltmenin yolu budur; merge_discard + merge_plan istemez. Asenkron: yanıt 202, plan planning durumundadır — merge_get ile planned bekleyin.",
|
|
86
106
|
inputSchema: { projectId: guid, planId: guid },
|
|
87
107
|
annotations: writes
|
|
88
108
|
}, handle(config, async ({ projectId, planId }) => {
|
|
89
109
|
const payload = await platform.org('POST', `/merges/${projectId}/${planId}/refresh`);
|
|
90
110
|
return {
|
|
91
111
|
...(payload && typeof payload === 'object' ? payload : { data: payload }),
|
|
92
|
-
note: "plan yeniden hesaplanıyor; merge_get ile status=planned bekleyin —
|
|
112
|
+
note: "plan yeniden hesaplanıyor; merge_get ile status=planned bekleyin — değişmeyen elemanların kararları korunur, yalnız değişenler yeniden sorulur"
|
|
93
113
|
};
|
|
94
114
|
}));
|
|
95
115
|
server.registerTool('merge_revert', {
|