@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.
Files changed (49) hide show
  1. package/dist/knowledge/componentCatalog.js +27 -97
  2. package/dist/knowledge/componentEvents.js +1 -18
  3. package/dist/knowledge/mobileComponentCatalog.js +114 -15
  4. package/dist/knowledge/mobileComponentEvents.js +41 -0
  5. package/dist/knowledge/motionResolve.js +252 -0
  6. package/dist/knowledge/normalize.js +87 -59
  7. package/dist/knowledge/pageDiscover.js +2 -2
  8. package/dist/knowledge/platform/batchOps.js +70 -0
  9. package/dist/knowledge/platform/heroTag.js +153 -0
  10. package/dist/knowledge/platform/lint.js +128 -6
  11. package/dist/knowledge/platform/mobile.js +1 -1
  12. package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
  13. package/dist/knowledge/platform/mobileRules.js +18 -0
  14. package/dist/knowledge/platform/pageTransition.js +57 -0
  15. package/dist/knowledge/platform/web.js +1 -1
  16. package/dist/knowledge/rules.js +52 -1
  17. package/dist/knowledge/styleResolveNative.js +88 -4
  18. package/dist/platform/capture/actions.js +4 -1
  19. package/dist/platform/capture/capture.js +10 -13
  20. package/dist/platform/capture/persistent-browser.js +130 -0
  21. package/dist/resources/examples/flow-list-to-form.json +8 -10
  22. package/dist/resources/examples/flow-load-table.json +1 -1
  23. package/dist/resources/examples/mobile-list-detail-form.json +4 -3
  24. package/dist/resources/examples/mobile-scan-code.json +285 -0
  25. package/dist/resources/guides/component.md +3 -3
  26. package/dist/resources/guides/expressions.md +2 -2
  27. package/dist/resources/guides/merge.md +98 -18
  28. package/dist/resources/guides/microservice.md +2 -2
  29. package/dist/resources/guides/mobile-layout.md +2 -1
  30. package/dist/resources/guides/mobile-publish.md +1 -0
  31. package/dist/resources/guides/mobile-steps.md +67 -0
  32. package/dist/resources/guides/mobile.md +27 -55
  33. package/dist/resources/guides/page.md +182 -121
  34. package/dist/resources/guides/translation.md +26 -0
  35. package/dist/resources/schema-fingerprint.json +2 -2
  36. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
  37. package/dist/tools/activities.js +3 -1
  38. package/dist/tools/applications.js +16 -2
  39. package/dist/tools/components.js +12 -6
  40. package/dist/tools/designSchemaPatch.js +18 -13
  41. package/dist/tools/layouts.js +7 -2
  42. package/dist/tools/merges.js +29 -9
  43. package/dist/tools/pages.js +129 -127
  44. package/dist/tools/platformInfo.js +3 -2
  45. package/dist/tools/profileWrite.js +2 -2
  46. package/dist/tools/schemaOps.js +132 -34
  47. package/dist/tools/screenWrite.js +1 -1
  48. package/dist/tools/screens.js +3 -3
  49. package/package.json +2 -2
@@ -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. 'tbl_vacancy__pager-next'). Kimlikler runtime'ın data-element-id özniteliğidir; ikinci bir mekanizma yoktur."
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, styleTargetTypesFrom, simulateLayoutOps, removeNodeFromTree, planEventRemovals, planResizes, planPropTypes, assertOpBodies, applySkeletonOps, reshapesSkeleton } from './schemaOps.js';
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 allOps = microOps(spec.tool, ops, operations).map(op => op);
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
- // Normalizasyon kayıttan SONRA koşar: stil anahtarı çevirisi hedefin komponent TİPİNE bakar
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
- const connectionIssues = opRecords.flatMap(entry => connectDataOpIssues(entry, connectedProps).map(issue => `${issue.code} — ${issue.message}`));
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
- const deletedFlows = orphanedFlowPlan(nodes, events, [...opRecords, ...removeEventOps]);
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
- const resized = resizeOps.length > 0 ? planResizes(nodes, resizeOps, spec.readTool) : { serverOps: [], applied: [], warnings: [] };
149
- const propTypes = propTypeOps.length > 0 ? planPropTypes(nodes, propTypeOps, spec.readTool) : { serverOps: [], applied: [] };
150
- const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...opRecords];
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),
@@ -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);
@@ -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: { projectId: guid, planId: guid, view: mergeView },
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 }) => platform.org('GET', `/merges/${projectId}/${planId}?view=${view ?? 'conflicts'}`)));
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 (source) ya da development (target) seçimini plana yazar. Yalnız status=planned iken çalışır. path boş bırakılırsa o kaydın tüm çakışmaları aynı tarafa çözülür. 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.",
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. KAYDEDİLMİŞ ÇAKIŞMA SEÇİMLERİ DÜŞER: yeni plan yeni elemanlar üretir, çözümler baştan verilir. 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.",
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 — çakışma çözümleri baştan verilir"
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', {