@appaflytech/zeyrek-ai-mcp 0.0.7 → 0.0.9

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 (54) hide show
  1. package/README.md +27 -21
  2. package/dist/config.js +2 -0
  3. package/dist/knowledge/backendConstraints.js +3 -0
  4. package/dist/knowledge/componentCatalog.js +172 -33
  5. package/dist/knowledge/componentEvents.js +35 -4
  6. package/dist/knowledge/errorPath.js +1 -1
  7. package/dist/knowledge/normalize.js +474 -23
  8. package/dist/knowledge/propTypes.js +5 -4
  9. package/dist/knowledge/rules.js +113 -6
  10. package/dist/knowledge/styleResolve.js +52 -5
  11. package/dist/knowledge/themeResolve.js +936 -0
  12. package/dist/knowledge/themeRules.js +221 -0
  13. package/dist/knowledge/themeSchema.js +335 -0
  14. package/dist/platform/domSummary.js +144 -0
  15. package/dist/platform/http.js +10 -2
  16. package/dist/platform/screenshot.js +492 -29
  17. package/dist/platform/screenshotMemory.js +55 -0
  18. package/dist/platform/strictArguments.js +32 -0
  19. package/dist/platform/writeDiff.js +6 -3
  20. package/dist/resources/examples/layout-app-shell.json +1 -1
  21. package/dist/resources/examples/page-sign-in.json +1 -1
  22. package/dist/resources/examples/theme-brand.json +162 -0
  23. package/dist/resources/guides/component.md +8 -2
  24. package/dist/resources/guides/expressions.md +6 -0
  25. package/dist/resources/guides/getting-started.md +6 -0
  26. package/dist/resources/guides/layout.md +36 -26
  27. package/dist/resources/guides/microservice.md +160 -6
  28. package/dist/resources/guides/page.md +165 -59
  29. package/dist/resources/guides/theme.md +210 -0
  30. package/dist/resources/guides/translation.md +13 -10
  31. package/dist/resources/guides/workflow.md +141 -7
  32. package/dist/resources/schema-fingerprint.json +2 -2
  33. package/dist/resources/schemas/CreateMicroserviceCommand.json +4 -1
  34. package/dist/resources/schemas/CreateWorkflowCommand.json +4 -1
  35. package/dist/resources/schemas/PreviewMicroserviceCommand.json +4 -1
  36. package/dist/resources/schemas/PreviewWorkflowCommand.json +4 -1
  37. package/dist/resources/schemas/StepDto.json +4 -1
  38. package/dist/resources/schemas/UpdateMicroserviceCommand.json +4 -1
  39. package/dist/resources/schemas/UpdateWorkflowCommand.json +4 -1
  40. package/dist/server.js +52 -42
  41. package/dist/tools/components.js +8 -0
  42. package/dist/tools/designSchemaPatch.js +152 -0
  43. package/dist/tools/flows.js +24 -21
  44. package/dist/tools/layouts.js +8 -8
  45. package/dist/tools/microservices.js +74 -10
  46. package/dist/tools/pages.js +275 -332
  47. package/dist/tools/projects.js +66 -9
  48. package/dist/tools/queries.js +72 -6
  49. package/dist/tools/schemaOps.js +280 -0
  50. package/dist/tools/screenWrite.js +2 -1
  51. package/dist/tools/screens.js +0 -7
  52. package/dist/tools/themes.js +163 -0
  53. package/dist/tools/workflows.js +10 -6
  54. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -1,41 +1,44 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { registerMicroserviceTools } from './tools/microservices.js';
3
- import { registerMicrofunctionTools } from './tools/microfunctions.js';
4
- import { registerApplicationTools } from './tools/applications.js';
5
- import { registerDeploymentTools } from './tools/deployments.js';
6
- import { registerDatasourceTools } from './tools/datasources.js';
7
- import { registerAutomationTools } from './tools/automations.js';
8
- import { registerOrganizationTools } from './tools/organizations.js';
9
- import { registerExternalComponentTools } from './tools/externalComponents.js';
10
- import { registerComponentTools } from './tools/components.js';
11
- import { registerPlatformTools } from './tools/platformInfo.js';
12
- import { registerTemplateTools } from './tools/templates.js';
13
- import { registerWorkflowTools } from './tools/workflows.js';
14
- import { registerLayoutTools } from './tools/layouts.js';
15
- import { registerAccessTools } from './tools/access.js';
16
- import { registerTranslationTools } from './tools/translations.js';
17
- import { registerFlowTools } from './tools/flows.js';
18
- import { registerKnowledgeResources } from './resources/knowledge.js';
19
- import { registerSchemaResources } from './resources/schemas.js';
20
- import { registerRuleResources } from './resources/rules.js';
21
- import { instrumentServer } from './observability/trace.js';
22
- import { registerProjectTools } from './tools/projects.js';
23
- import { registerEntityTools } from './tools/entities.js';
24
- import { registerDependencyTools } from './tools/dependencies.js';
25
- import { registerVariableTools } from './tools/variables.js';
26
- import { registerFunctionTools } from './tools/functions.js';
27
- import { registerFolderTools } from './tools/folders.js';
28
- import { registerQueryTools } from './tools/queries.js';
29
- import { registerViewTools } from './tools/views.js';
30
- import { registerPageTools } from './tools/pages.js';
31
- import { registerFileTools } from './tools/files.js';
32
- import { registerRowTools } from './tools/rows.js';
33
- import { registerImportTools } from './tools/imports.js';
34
- import { registerEnvironmentTools } from './tools/environments.js';
35
- import { registerMergeTools } from './tools/merges.js';
36
- import { registerRevisionTools } from './tools/revisions.js';
37
- import { registerActivityTools } from './tools/activities.js';
38
- import { registerDtoTools } from './tools/dtos.js';
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { registerMicroserviceTools } from "./tools/microservices.js";
3
+ import { registerMicrofunctionTools } from "./tools/microfunctions.js";
4
+ import { registerApplicationTools } from "./tools/applications.js";
5
+ import { registerDeploymentTools } from "./tools/deployments.js";
6
+ import { registerDatasourceTools } from "./tools/datasources.js";
7
+ import { registerAutomationTools } from "./tools/automations.js";
8
+ import { registerOrganizationTools } from "./tools/organizations.js";
9
+ import { registerExternalComponentTools } from "./tools/externalComponents.js";
10
+ import { registerComponentTools } from "./tools/components.js";
11
+ import { registerPlatformTools } from "./tools/platformInfo.js";
12
+ import { registerTemplateTools } from "./tools/templates.js";
13
+ import { registerWorkflowTools } from "./tools/workflows.js";
14
+ import { registerLayoutTools } from "./tools/layouts.js";
15
+ import { registerDesignSchemaPatchTool } from "./tools/designSchemaPatch.js";
16
+ import { registerAccessTools } from "./tools/access.js";
17
+ import { registerTranslationTools } from "./tools/translations.js";
18
+ import { registerFlowTools } from "./tools/flows.js";
19
+ import { registerKnowledgeResources } from "./resources/knowledge.js";
20
+ import { registerSchemaResources } from "./resources/schemas.js";
21
+ import { registerRuleResources } from "./resources/rules.js";
22
+ import { instrumentServer } from "./observability/trace.js";
23
+ import { rejectUnknownArguments } from "./platform/strictArguments.js";
24
+ import { registerProjectTools } from "./tools/projects.js";
25
+ import { registerEntityTools } from "./tools/entities.js";
26
+ import { registerDependencyTools } from "./tools/dependencies.js";
27
+ import { registerVariableTools } from "./tools/variables.js";
28
+ import { registerFunctionTools } from "./tools/functions.js";
29
+ import { registerFolderTools } from "./tools/folders.js";
30
+ import { registerQueryTools } from "./tools/queries.js";
31
+ import { registerViewTools } from "./tools/views.js";
32
+ import { registerPageTools } from "./tools/pages.js";
33
+ import { registerThemeTools } from "./tools/themes.js";
34
+ import { registerFileTools } from "./tools/files.js";
35
+ import { registerRowTools } from "./tools/rows.js";
36
+ import { registerImportTools } from "./tools/imports.js";
37
+ import { registerEnvironmentTools } from "./tools/environments.js";
38
+ import { registerMergeTools } from "./tools/merges.js";
39
+ import { registerRevisionTools } from "./tools/revisions.js";
40
+ import { registerActivityTools } from "./tools/activities.js";
41
+ import { registerDtoTools } from "./tools/dtos.js";
39
42
  const moduleRegistrations = {
40
43
  platform: registerPlatformTools,
41
44
  project: registerProjectTools,
@@ -52,10 +55,17 @@ const moduleRegistrations = {
52
55
  dto: registerDtoTools,
53
56
  view: registerViewTools,
54
57
  page: registerPageTools,
55
- component: registerComponentTools,
58
+ theme: registerThemeTools,
59
+ component: (server, platform) => {
60
+ registerComponentTools(server, platform);
61
+ registerDesignSchemaPatchTool(server, platform, "component");
62
+ },
56
63
  externalComponent: registerExternalComponentTools,
57
64
  flow: registerFlowTools,
58
- layout: registerLayoutTools,
65
+ layout: (server, platform) => {
66
+ registerLayoutTools(server, platform);
67
+ registerDesignSchemaPatchTool(server, platform, "layout");
68
+ },
59
69
  variable: registerVariableTools,
60
70
  function: registerFunctionTools,
61
71
  folder: registerFolderTools,
@@ -69,10 +79,10 @@ const moduleRegistrations = {
69
79
  environment: registerEnvironmentTools,
70
80
  merge: registerMergeTools,
71
81
  activity: registerActivityTools,
72
- revision: registerRevisionTools
82
+ revision: registerRevisionTools,
73
83
  };
74
84
  export function createGatewayServer(platform) {
75
- const server = instrumentServer(new McpServer({ name: 'ngapps', version: '0.1.0' }), platform.config);
85
+ const server = rejectUnknownArguments(instrumentServer(new McpServer({ name: "ngapps", version: "0.1.0" }), platform.config));
76
86
  for (const moduleName of platform.config.modules) {
77
87
  moduleRegistrations[moduleName](server, platform);
78
88
  }
@@ -1,6 +1,7 @@
1
1
  import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
2
2
  import { normalizeComponentDefaults, componentDefaultsNote, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, withNormalization } from '../knowledge/normalize.js';
3
3
  import { eventContractsFor, eventPayloadNote } from '../knowledge/componentEvents.js';
4
+ import { componentPresetDefaults, readProjectTheme } from '../knowledge/themeSchema.js';
4
5
  import { knowledgeFor, knowledgeNote, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
5
6
  import { componentUpdateBody, fetchHost, schemaParam, unwrap } from './screens.js';
6
7
  import { PlatformApiError } from '../platform/errors.js';
@@ -160,6 +161,12 @@ export function registerComponentTools(server, platform) {
160
161
  : [];
161
162
  const componentSlug = typeof component.slug === 'string' ? component.slug : undefined;
162
163
  const knowledge = knowledgeFor(componentSlug);
164
+ // S-3 — `defaults` HAM katalogdur; Studio'da bırakılan düğüm ise temanın bileşen preseti
165
+ // kaplanmış katalogdan kurulur (`withThemeComponentDefaults`). Tema okunamazsa (uç düşer,
166
+ // yetki yoksa) alan yanıta HİÇ girmez: uydurulmuş bir varsayılan yanlış bilgidir.
167
+ const themeDefaults = componentSlug === undefined
168
+ ? undefined
169
+ : componentPresetDefaults(readProjectTheme(await platform.org('GET', `/p/${platform.resolveProject(project)}`).catch(() => undefined)), componentSlug);
163
170
  return {
164
171
  id: component.id,
165
172
  slug: component.slug,
@@ -188,6 +195,7 @@ export function registerComponentTools(server, platform) {
188
195
  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."
189
196
  }
190
197
  : {}),
198
+ ...(themeDefaults ? { themeDefaults } : {}),
191
199
  appearanceValues: APPEARANCE_TOKEN_SETS,
192
200
  stateFlags: STATE_FLAG_NOTES,
193
201
  validations: definition.validations,
@@ -0,0 +1,152 @@
1
+ import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, normalizeSchemaOps, validationNote, 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, planResizes, planPropTypes, assertOpBodies } from './schemaOps.js';
3
+ import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
4
+ import { fetchHost, hostPath } from './screens.js';
5
+ import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
6
+ import { schemaOpDrops, withWriteDiff } from '../platform/writeDiff.js';
7
+ import { fetchComponentEventNames } from './components.js';
8
+ import { PlatformApiError } from '../platform/errors.js';
9
+ import { handle } from '../platform/responses.js';
10
+ import { slotWarning } from './layouts.js';
11
+ import { z } from 'zod';
12
+ const HOSTS = {
13
+ layout: {
14
+ tool: 'layout_schema_patch',
15
+ readTool: 'layout_schema_get',
16
+ tree: 'views',
17
+ kind: 'layout',
18
+ codes: { gap: 'LAY-003 (uyarı)', flexMismatch: 'LAY-007 (uyarı)', flexInvalid: 'LAY-008 (uyarı)' },
19
+ title: "Layout şemasına micro-op uygula",
20
+ description: "page_schema_patch'in layout karşılığı: micro-op'ları platformun PATCH /layouts/{app}/{id}/schema ucuna TEK istekle yazar; sunucu sayfa ile AYNI op sözlüğünü ve doğrulama kapsamını kullanır (dokunulan düğümler tam doğrulanır, geri kalan ağaç yapısal). İstek atomiktir: bir op reddedilirse hiçbiri yazılmaz. Op sözlüğü page_schema_patch ile birebir aynıdır; farklar: kök ekleme 'root' parent'ına gider (page_container yoktur), remove_event target:'page' layout düzeyi olayları hedefler, baseVersion yoktur (son yazan kazanır — hedefleri layout_schema_get ile okuyun). Slot'u silen ya da ikinci slot ekleyen yama LAY-002/LAY-006 uyarısı alır. Layout'un adı, parametreleri ve düz JSON Patch değişiklikleri için layout_patch; tam ağaç için layout_update."
21
+ },
22
+ component: {
23
+ tool: 'component_schema_patch',
24
+ readTool: 'component_get',
25
+ tree: 'schema',
26
+ kind: 'widget',
27
+ codes: { gap: 'PAGE-010 (uyarı)', flexMismatch: 'PAGE-017 (uyarı)', flexInvalid: 'PAGE-018 (uyarı)' },
28
+ title: 'Widget şemasına micro-op uygula',
29
+ description: "page_schema_patch'in widget karşılığı: widget kaydının (kind:'widget') kendi ağacına micro-op'ları platformun PATCH /components/{app}/{id}/schema ucuna TEK istekle yazar; sunucu sayfa ile AYNI op sözlüğünü ve doğrulama kapsamını kullanır. İstek atomiktir: bir op reddedilirse hiçbiri yazılmaz. Op sözlüğü page_schema_patch ile aynıdır; farklar: kök ekleme 'root' parent'ına gider, widget'ın düzey olayı olmadığı için remove_event yalnız komponent hedefler, baseVersion yoktur. id widget kaydının id'sidir (component_list / component_get). component_update tam gövde PUT'tur ve slug alır; ağaçta hedefli değişiklik için bu aracı kullanın."
30
+ }
31
+ };
32
+ /**
33
+ * Layout ve widget ağaçlarının micro-op yazması. Sayfa hattıyla (`page_schema_patch`) aynı istek
34
+ * öncesi kapılar koşar; sayfaya özgü olanlar (page_container koruması, kökten page_container'a
35
+ * yönlendirme, baseVersion) düşer. Kayıt her yamada BİR kez okunur: PAGE-008, G-21 ve boyut/tip
36
+ * planlaması okunan ağaca bakar, platformda sürüm koşulu olmadığı için okuma bir kilit değildir.
37
+ */
38
+ export function registerDesignSchemaPatchTool(server, platform, host) {
39
+ const config = platform.config;
40
+ const spec = HOSTS[host];
41
+ server.registerTool(spec.tool, {
42
+ title: spec.title,
43
+ description: spec.description,
44
+ inputSchema: {
45
+ project: projectParam,
46
+ environment: environmentParam,
47
+ applicationId: guid,
48
+ id: guid.describe(host === 'layout' ? "Layout id'si" : "Widget kaydının id'si (slug değil)"),
49
+ ops: opsParam.optional(),
50
+ operations: opsParam.optional().describe("'ops' ile aynı alan (eşanlamlı)"),
51
+ allowFlowDeletion: z.boolean().optional().describe(`true: bu yama bir akışı referanssız bırakacaksa yine de uygulanır. Varsayılan false — platform ${host === 'layout' ? 'layout' : 'widget'} kaydında da referanssız akışı KALICI siler ve gateway isteği önden reddeder (G-21).`),
52
+ view: writeViewParam
53
+ },
54
+ annotations: updates
55
+ }, handle(config, async ({ project, environment, applicationId, id, ops, operations, allowFlowDeletion, view }) => {
56
+ const allOps = microOps(spec.tool, ops, operations).map(op => op);
57
+ const removeEventOps = allOps.filter(entry => entry.op === 'remove_event');
58
+ const resizeOps = allOps.filter(entry => entry.op === 'resize_component');
59
+ const propTypeOps = allOps.filter(entry => entry.op === 'set_prop_type');
60
+ const opRecords = allOps.filter(entry => !['remove_event', 'resize_component', 'set_prop_type'].includes(String(entry.op)));
61
+ const normalized = normalizeSchemaOps(opRecords);
62
+ assertOpBodies(opRecords, propTypeOps);
63
+ const record = await fetchHost(platform, host, project, environment, applicationId, id);
64
+ // Platform ucu kaydın türüne bakmaz: sıradan bir komponentin id'si verilirse kataloğundaki
65
+ // tanımın yanına bir şema yazılırdı.
66
+ if (host === 'component' && record.kind !== 'widget' && !record.widgetType) {
67
+ throw new PlatformApiError(`component_schema_patch yalnız widget kaydına yazar; '${String(record.slug ?? id)}' bir widget değil (kind: ${String(record.kind ?? 'yok')}). Komponent kaydını component_update ile güncelleyin.`, 400);
68
+ }
69
+ const nodes = (record[spec.tree] ?? []);
70
+ const events = host === 'layout' ? record.events : undefined;
71
+ if (opRecords.some(entry => entry.op === 'set_event')) {
72
+ const typesByElementId = new Map();
73
+ collectTypes(nodes, typesByElementId);
74
+ collectTypes(opRecords.map(entry => entry.component).filter(Boolean), typesByElementId);
75
+ const eventsBySlug = await fetchComponentEventNames(platform, project, environment, applicationId).catch(() => new Map());
76
+ const unknown = unknownEventOps(opRecords, typesByElementId, eventsBySlug);
77
+ if (unknown.length > 0)
78
+ throw new PlatformApiError(`PAGE-008 — geçersiz olay adı (istek gönderilmedi): ${unknown.join(' · ')}`, 400);
79
+ }
80
+ const deletedFlows = orphanedFlowPlan(nodes, events, [...opRecords, ...removeEventOps]);
81
+ if (deletedFlows.length > 0 && allowFlowDeletion !== true)
82
+ throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
83
+ const added = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component);
84
+ assertScreenWriteGuards(spec.kind, { schema: added });
85
+ const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, added);
86
+ const flowWarnings = opRecords.some(entry => entry.op === 'remove_component')
87
+ ? [...orphanedFlowWarnings(nodes, events, opRecords), ...staleSetterWarnings(Array.isArray(record.flows) ? record.flows : [], opRecords, nodes)]
88
+ : [];
89
+ const simulated = structuredClone(nodes);
90
+ for (const entry of opRecords.filter(entry => entry.op === 'remove_component'))
91
+ removeNodeFromTree(simulated, String(entry.target ?? ''));
92
+ const touched = simulateLayoutOps(simulated, opRecords);
93
+ // Sayfadaki `simulatable` ile aynı küme: iç içe repeater ancak ekleme/taşıma ile doğar, kontrol
94
+ // her yamada koşsaydı kayıtlı ağacında zaten iç içe repeater olan layout hiç yamalanamazdı.
95
+ const simulatable = opRecords.some(entry => ['add_component', 'move_component'].includes(String(entry.op))
96
+ || (entry.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(entry.prop ?? ''))));
97
+ const nested = simulatable ? nestedRepeaterNodes(simulated) : [];
98
+ if (nested.length > 0)
99
+ throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
100
+ const reshaped = simulatable || opRecords.some(entry => entry.op === 'remove_component');
101
+ const layoutWarnings = reshaped
102
+ ? [
103
+ ...verticalGapWarnings(simulated, spec.codes.gap, touched),
104
+ ...stringStyleWarning(stringStyleNodes(simulated)),
105
+ ...inertFlexPropWarning(inertFlexPropNodes(simulated)),
106
+ ...flexSizeWarnings(simulated, spec.codes.flexMismatch, spec.codes.flexInvalid),
107
+ ...(host === 'layout' ? slotWarning(simulated) : [])
108
+ ]
109
+ : [];
110
+ const removedEvents = planEventRemovals(removeEventOps);
111
+ const resized = resizeOps.length > 0 ? planResizes(nodes, resizeOps, spec.readTool) : { serverOps: [], applied: [], warnings: [] };
112
+ const propTypes = propTypeOps.length > 0 ? planPropTypes(nodes, propTypeOps, spec.readTool) : { serverOps: [], applied: [] };
113
+ const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...opRecords];
114
+ const response = ((await platform.tenant('PATCH', project, `${hostPath(host, applicationId, id)}/schema`, {
115
+ environment,
116
+ body: { ops: serverOps }
117
+ })) ?? {});
118
+ if (view !== 'full')
119
+ outlineWriteResponse(response);
120
+ if (removedEvents.length > 0)
121
+ response.removedEvents = removedEvents;
122
+ if (propTypes.applied.length > 0)
123
+ response.propTypes = propTypes.applied;
124
+ if (resized.applied.length > 0)
125
+ response.resized = resized.applied;
126
+ if (resized.warnings.length > 0)
127
+ response.resizeWarnings = resized.warnings;
128
+ if (deletedFlows.length > 0)
129
+ response.deletedFlows = deletedFlows;
130
+ if (flowWarnings.length > 0)
131
+ response.flowWarnings = flowWarnings;
132
+ if (layoutWarnings.length > 0)
133
+ response.layoutWarnings = layoutWarnings;
134
+ const contentOps = opRecords.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
135
+ let opDrops = [];
136
+ if (contentOps.length > 0) {
137
+ try {
138
+ const saved = await fetchHost(platform, host, project, environment, applicationId, id);
139
+ opDrops = schemaOpDrops(saved[spec.tree], contentOps);
140
+ }
141
+ catch {
142
+ opDrops = [];
143
+ }
144
+ }
145
+ return withWriteDiff(withNormalization(response, [
146
+ ...componentDefaultsNote(normalized.components),
147
+ ...validationNote(normalized.validations),
148
+ ...catalogNotes,
149
+ ...replacePropNote(normalized.props)
150
+ ]), opDrops);
151
+ }));
152
+ }
@@ -1,7 +1,7 @@
1
- import { fetchHost, flowPath, hostParam, hostUpdateBody, unwrap } from './screens.js';
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, unknownSetterTargetWarnings, withNormalization } from '../knowledge/normalize.js';
4
+ import { 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';
@@ -42,10 +42,6 @@ function flowSummary(flow) {
42
42
  errorSteps: Array.isArray(flow.errorSteps) ? flow.errorSteps.length : 0
43
43
  };
44
44
  }
45
- async function hostEnvelope(platform, host, project, environment, applicationId, hostId) {
46
- const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
47
- return { record, envelope: { [host]: hostUpdateBody(host, { ...record, applicationId }) } };
48
- }
49
45
  /**
50
46
  * FLOW-024'ün arayacağı düğüm kümesi. Ham kaydın TAMAMI gezilmez: kayıt kendi `flows`'unu da
51
47
  * taşıyabilir ve oradaki bayat element id'leri kümeyi kirletip aramayı sessizce boşa çıkarır.
@@ -243,13 +239,19 @@ export function registerFlowTools(server, platform) {
243
239
  }));
244
240
  server.registerTool('flow_get', {
245
241
  title: 'Akış getir',
246
- description: 'Tek akışı adımlarıyla getirir; yeni akış yazarken en iyi şablon budur.',
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.",
247
243
  inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
248
244
  annotations: readOnly
249
- }, handle(config, ({ project, environment, host, applicationId, hostId, flowId }) => platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment })));
245
+ }, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
246
+ const response = await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment });
247
+ const warnings = targetlessSetterWarnings(response);
248
+ if (warnings.length === 0 || !response || typeof response !== 'object' || Array.isArray(response))
249
+ return response;
250
+ return { ...response, flowWarnings: warnings };
251
+ }));
250
252
  server.registerTool('flow_create', {
251
253
  title: 'Akış oluştur',
252
- description: "Sayfa/layout/komponent için olay akışı oluşturur. Platform bu uçta barındırıcının TAM gövdesini de ister (akış ile şemadaki olay bağı tek işlemde tutarlı kalsın diye); gateway barındırıcıyı okuyup gövdeye kendisi koyar. Akışı bir komponent olayına bağlamak ayrı adımdır: page_schema_patch ile set_event {target, event, eventValue: <flow id>}.",
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ı yalnız setter hedeflerini denetlemek için okur (FLOW-024). 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.",
253
255
  inputSchema: {
254
256
  project: projectParam,
255
257
  environment: environmentParam,
@@ -265,7 +267,7 @@ export function registerFlowTools(server, platform) {
265
267
  },
266
268
  annotations: writes
267
269
  }, handle(config, async ({ project, environment, host, applicationId, hostId, name, slug, steps, errorSteps, flowId, policyOverride }) => {
268
- const { record: hostRecord, envelope } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
270
+ const hostRecord = await fetchHost(platform, host, project, environment, applicationId, hostId);
269
271
  const resolvedFlowId = flowId ?? randomUUID();
270
272
  const flow = {
271
273
  id: resolvedFlowId,
@@ -283,7 +285,10 @@ export function registerFlowTools(server, platform) {
283
285
  ...flowBodyWarnings(steps),
284
286
  ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord))
285
287
  ];
286
- await platform.tenant('POST', project, flowPath(host, applicationId, hostId), { environment, body: { flow, ...envelope } });
288
+ // Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
289
+ // ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
290
+ // eskitir. Kayıt yalnız FLOW-024 hedef araması için okunur.
291
+ await platform.tenant('POST', project, flowPath(host, applicationId, hostId), { environment, body: { flow } });
287
292
  // Diff YALNIZ çağıranın yazdığı alanları kapsar; `id` ve barındırıcı bağı (`pageId` vb.)
288
293
  // gövdeye gateway koyar, kullanıcının düzeltebileceği bir şey değildir.
289
294
  const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, resolvedFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps });
@@ -292,12 +297,12 @@ export function registerFlowTools(server, platform) {
292
297
  hostId,
293
298
  flow: { id: resolvedFlowId, name, slug, steps: steps.length },
294
299
  ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
295
- hint: "Akışı bir olaya bağlamak için: page_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."
300
+ 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."
296
301
  }, normalized), drops), findings);
297
302
  }));
298
303
  server.registerTool('flow_update', {
299
304
  title: 'Akış güncelle',
300
- description: 'Akışın adını/adımlarını değiştirir. Gövde replace\'tir: verilen steps listesi akışın yeni tam hâlidir. Barındırıcı gövdesini gateway ekler.',
305
+ description: 'Akışın adını/adımlarını değiştirir. Gövde replace\'tir: verilen steps listesi akışın yeni tam hâlidir. Yalnız akış yazılır; barındırıcının şeması değişmez.',
301
306
  inputSchema: {
302
307
  project: projectParam,
303
308
  environment: environmentParam,
@@ -316,7 +321,7 @@ export function registerFlowTools(server, platform) {
316
321
  const current = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
317
322
  if (!current.id)
318
323
  throw new PlatformApiError(`'${flowId}' id'li akış bulunamadı; flow_list ile doğrulayın.`);
319
- const { record: hostRecord, envelope } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
324
+ const hostRecord = await fetchHost(platform, host, project, environment, applicationId, hostId);
320
325
  const flow = {
321
326
  id: flowId,
322
327
  name: name ?? current.name,
@@ -336,7 +341,7 @@ export function registerFlowTools(server, platform) {
336
341
  ...flowBodyWarnings(flow.steps),
337
342
  ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
338
343
  ];
339
- await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow, ...envelope } });
344
+ await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
340
345
  // Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
341
346
  // "düştü" demek yanlış alarm olurdu.
342
347
  const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, {
@@ -370,7 +375,7 @@ export function registerFlowTools(server, platform) {
370
375
  const document = structuredClone(current);
371
376
  const resolved = resolvePatchOperations(document, operations, 'flow_get');
372
377
  const patched = fastJsonPatch.applyPatch(document, resolved.operations, true, false).newDocument;
373
- const { record: hostRecord } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
378
+ const hostRecord = await fetchHost(platform, host, project, environment, applicationId, hostId);
374
379
  const flow = {
375
380
  ...patched,
376
381
  id: flowId,
@@ -386,8 +391,7 @@ export function registerFlowTools(server, platform) {
386
391
  ...flowBodyWarnings(flow.steps),
387
392
  ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
388
393
  ];
389
- // Gövde değil FARK gönderilir: barındırıcı zarfı (envelope) yazma yolunda gerekmez, platform
390
- // PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
394
+ // Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
391
395
  const netOps = fastJsonPatch.compare(current, flow);
392
396
  const saved = await platform.tenant('PATCH', project, flowPath(host, applicationId, hostId, flowId), {
393
397
  environment,
@@ -407,12 +411,11 @@ export function registerFlowTools(server, platform) {
407
411
  }));
408
412
  server.registerTool('flow_delete', {
409
413
  title: 'Akış sil',
410
- description: "Akışı siler. Bu uç GÖVDE ister (barındırıcı komutu) — gövdesiz silme şemadaki olay bağını sarkıtır; gateway gövdeyi kendisi kurar. Olay bağını da temizlemek için ayrıca page_schema_patch set_event ile eventValue'yu boşaltın.",
414
+ description: "Akışı siler; gövde yalnız akış id'sidir ve barındırıcının şemasına dokunmaz, yani olay bağı kendiliğinden KALKMAZ. Doğru sıra: önce bağı remove_event ile kaldırın (sayfada page_schema_patch, layout'ta layout_schema_patch, widget'ta component_schema_patch). Bağı kalmayan akışı platform o yamada zaten kalıcı siler (yama allowFlowDeletion:true ister); ardından flow_delete no-op'tur. Önce flow_delete çağırırsanız şemada ölü bağ kalır.",
411
415
  inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
412
416
  annotations: destructive
413
417
  }, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
414
- const { envelope } = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
415
- await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId, ...envelope } });
418
+ await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId } });
416
419
  return { deleted: flowId, host, hostId };
417
420
  }));
418
421
  }
@@ -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 { normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
5
+ import { normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
6
6
  import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode } from './screens.js';
7
7
  import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
8
8
  import { applyPropTypesToSchema, isSetPropTypeOp, setPropTypeErrors } from '../knowledge/propTypes.js';
@@ -12,7 +12,7 @@ import { handle } from '../platform/responses.js';
12
12
  import { z } from 'zod';
13
13
  const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
14
14
  .describe("JSON Patch listesi (RFC 6902): { op, path, value }. Yol seçici alabilir — '/views/0/children[elementId=baslik]/props/content/data'.");
15
- const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir ve sayfa micro-op hattı (page_schema_patch) layout'a uygulanmaz. İç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).");
15
+ const viewsParam = schemaParam.describe("Layout'un komponent ağacı. 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).");
16
16
  function countSlots(nodes) {
17
17
  if (!Array.isArray(nodes))
18
18
  return 0;
@@ -23,7 +23,7 @@ function countSlots(nodes) {
23
23
  return total + (record.type === 'slot' ? 1 : 0) + countSlots(record.children);
24
24
  }, 0);
25
25
  }
26
- function slotWarning(views) {
26
+ export function slotWarning(views) {
27
27
  const slots = countSlots(views);
28
28
  if (slots === 0) {
29
29
  return ["LAY-002: layout'ta 'slot' tipli komponent YOK — Studio ve runtime, sayfa şemasını yalnız layout'taki slot'un içine yerleştirir (page.mappers: layout varsa render şeması layout views'üdür); bu layout'a bağlanan her sayfanın içeriği görünmez olur. views'e bir { type: 'slot' } komponenti ekleyin ve page_state.layoutSlots ile doğrulayın."];
@@ -49,7 +49,7 @@ export function registerLayoutTools(server, platform) {
49
49
  }, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('GET', project, `/layouts/${applicationId}/${id}`, { environment })));
50
50
  server.registerTool('layout_schema_get', {
51
51
  title: "Layout komponent ağacını oku",
52
- description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Platformda layout micro-op ucu (sayfadaki PATCH /pages/.../schema karşılığı) yoktur, ama PATCH /layouts/{app}/{id} vardır: hedefli değişiklik için layout_patch kullanın — gateway okur, JSON Patch'i çözer ve FARKI `{ baseVersion, ops }` olarak platforma yazar; tam gövde PUT'u atılmaz. Buradaki updatedAt'i ayrıca expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update.",
52
+ description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Ağaçta hedefli değişiklik için layout_schema_patch (sayfa ile aynı micro-op sözlüğü, PATCH /layouts/{app}/{id}/schema). Ad/parametre/olay alanlarında ya da düz JSON Patch yolu gerektiğinde layout_patch — o araç buradaki updatedAt'i expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update.",
53
53
  inputSchema: {
54
54
  project: projectParam,
55
55
  environment: environmentParam,
@@ -93,7 +93,7 @@ export function registerLayoutTools(server, platform) {
93
93
  assertScreenWriteGuards('layout', { schema: views, events });
94
94
  const fixed = normalizeComponentDefaults(views);
95
95
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, views);
96
- const gaps = [...verticalGapWarnings(views ?? [], 'LAY-003 (uyarı)'), ...flexSizeWarnings(views ?? [], 'LAY-007 (uyarı)', 'LAY-008 (uyarı)')];
96
+ const gaps = [...verticalGapWarnings(views ?? [], 'LAY-003 (uyarı)'), ...flexSizeWarnings(views ?? [], 'LAY-007 (uyarı)', 'LAY-008 (uyarı)'), ...spacingLayerWarnings(views ?? [], 'LAY-010 (uyarı)', 'LAY-011 (uyarı)')];
97
97
  const response = await platform.tenant('POST', project, `/layouts/${applicationId}`, {
98
98
  environment,
99
99
  body: { name, views: views ?? [], events: events ?? [], parameters: parameters ?? null }
@@ -126,7 +126,7 @@ export function registerLayoutTools(server, platform) {
126
126
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.views);
127
127
  const body = layoutUpdateBody({ ...current, applicationId }, overrides);
128
128
  await platform.tenant('PUT', project, `/layouts/${applicationId}/${id}`, { environment, body });
129
- const gaps = overrides.views === undefined ? [] : [...verticalGapWarnings(body.views, 'LAY-003 (uyarı)'), ...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)')];
129
+ const gaps = overrides.views === undefined ? [] : [...verticalGapWarnings(body.views, 'LAY-003 (uyarı)'), ...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)'), ...spacingLayerWarnings(body.views, 'LAY-010 (uyarı)', 'LAY-011 (uyarı)')];
130
130
  const summary = { id, changed: Object.keys(overrides), preserved: { viewNodes: body.views.length } };
131
131
  if (gaps.length > 0)
132
132
  summary.layoutWarnings = gaps;
@@ -134,7 +134,7 @@ export function registerLayoutTools(server, platform) {
134
134
  }));
135
135
  server.registerTool('layout_patch', {
136
136
  title: "Layout'a patch uygula",
137
- description: "Tam ağacı bağlama almadan hedefli değişiklik: gateway layout'u çeker, JSON Patch'i ve kendi normalizasyonlarını uygular, doğrulamadan geçirir ve FARKI platformun PATCH /layouts/{app}/{id} ucuna `{ baseVersion, ops }` olarak yazar — tek başlık düzeltmesi için 12 düğümlük ağacı yeniden göndermeniz gerekmez (09-04 Y-25) ve tam gövde PUT'u ATILMAZ. Yol İNDEKS yerine SEÇİCİ alabilir: '/views/0/children[elementId=baslik]/props/content/data'. Sürüm kontrolü platformda: okunan kaydın `version` değeri `baseVersion` olarak gider, araya başka bir yazma girdiyse 409 döner ve HİÇBİR op uygulanmaz. expectedUpdatedAt verirseniz gateway ayrıca istek gitmeden önce yerel kontrol yapar. Ayrıca 'set_prop_type' micro-op'unu tanır ({target, prop, propType: 'static'|'code'|'translation', ref?, data?}) — layout header/sidebar etiketini çeviriye bağlamanın yolu budur; JSON Patch ile aynı listede karışık verilebilir, micro-op'lar önce uygulanır.",
137
+ description: "RFC 6902 JSON Patch ile hedefli değişiklik: gateway layout'u çeker, JSON Patch'i ve kendi normalizasyonlarını uygular, doğrulamadan geçirir ve FARKI platformun PATCH /layouts/{app}/{id} ucuna `{ baseVersion, ops }` olarak yazar — tam gövde PUT'u ATILMAZ (09-04 Y-25). Ağaçta düğüm ekleme/silme/taşıma, prop, olay ve boyut değişikliği için önce layout_schema_patch'i düşünün: sayfa ile aynı micro-op sözlüğüdür ve dokunulan düğümleri platform doğrular; bu araç ad/parametre/olay alanları ve düz yol gerektiren durumlar içindir. Yol İNDEKS yerine SEÇİCİ alabilir: '/views/0/children[elementId=baslik]/props/content/data'. Sürüm kontrolü platformda: okunan kaydın `version` değeri `baseVersion` olarak gider, araya başka bir yazma girdiyse 409 döner ve HİÇBİR op uygulanmaz. expectedUpdatedAt verirseniz gateway ayrıca istek gitmeden önce yerel kontrol yapar. Ayrıca 'set_prop_type' micro-op'unu tanır ({target, prop, propType: 'static'|'code'|'translation', ref?, data?}); JSON Patch ile aynı listede karışık verilebilir, micro-op'lar önce uygulanır. Yalnız etiket çeviriye bağlanacaksa aynı op layout_schema_patch'te platformun kendi op'u olarak çalışır.",
138
138
  inputSchema: {
139
139
  project: projectParam,
140
140
  environment: environmentParam,
@@ -193,7 +193,7 @@ export function registerLayoutTools(server, platform) {
193
193
  body: { baseVersion: baseVersionOf(document, 'layout_get'), ops: netOps }
194
194
  });
195
195
  const written = patchResult(saved, finalDocument);
196
- const gaps = [...verticalGapWarnings(body.views, 'LAY-003 (uyarı)'), ...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)')];
196
+ const gaps = [...verticalGapWarnings(body.views, 'LAY-003 (uyarı)'), ...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)'), ...spacingLayerWarnings(body.views, 'LAY-010 (uyarı)', 'LAY-011 (uyarı)')];
197
197
  const summary = {
198
198
  id,
199
199
  applied: patchOps.length,