@appaflytech/zeyrek-ai-mcp 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -252,7 +252,9 @@ const BASE_TOKENS = {
252
252
  };
253
253
  export const TOKENS = BASE_TOKENS;
254
254
  /** Palet gruplarının anahtarları — paletin KENDİSİNDEN türer ki listeyi ikinci kez yazmayalım. */
255
- export const PALETTE_GROUPS = Object.fromEntries(Object.entries(WHITE_PALETTE).map(([group, values]) => [group, Object.keys(values)]));
255
+ /** Tabanda BULUNMAYAN, yalnız override olarak yazılabilen anahtarlar (f-08: `accent.onPrimary` → `--ng-text-on-accent`). */
256
+ export const OPTIONAL_PALETTE_KEYS = { accent: ['onPrimary'] };
257
+ export const PALETTE_GROUPS = Object.fromEntries(Object.entries(WHITE_PALETTE).map(([group, values]) => [group, [...Object.keys(values), ...(OPTIONAL_PALETTE_KEYS[group] ?? [])]]));
256
258
  /* ---- Birleştiriciler ve ölçekler (`utils.ts` kopyası) ---- */
257
259
  export function mergePalette(base, overrides) {
258
260
  const result = { ...base };
@@ -685,7 +687,7 @@ export function buildThemeStyle(palette, form, t, shadow, formDefaultSize) {
685
687
  --ng-action-yellow: ${actions.yellow || colors.yellow.r600};
686
688
  --ng-action-white: ${actions.white || palette.background.surface};
687
689
  --ng-action-black: ${actions.black || colors.grey.r900};
688
- --ng-text-on-accent: ${getContrastColor(palette.accent.primary)};
690
+ --ng-text-on-accent: ${palette.accent.onPrimary || getContrastColor(palette.accent.primary)};
689
691
  `;
690
692
  }
691
693
  /** Temanın yaydığı değişken adlarının tam listesi — elle tutulmaz, gerçek şablondan türer. */
@@ -177,6 +177,79 @@ function diagnoseKnownFailure(status, message, code, context) {
177
177
  }
178
178
  return '';
179
179
  }
180
+ const MaxErrorCauseWalk = 32;
181
+ const MaxErrorPathShown = 6;
182
+ const MaxErrorTextLength = 400;
183
+ function nonEmptyText(value) {
184
+ if (typeof value !== 'string' || value.trim() === '')
185
+ return undefined;
186
+ return value.length > MaxErrorTextLength ? `${value.slice(0, MaxErrorTextLength)}…` : value;
187
+ }
188
+ function asRecord(value) {
189
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
190
+ }
191
+ function errorLevels(body) {
192
+ const levels = [];
193
+ let current = asRecord(body);
194
+ while (current) {
195
+ if (levels.length === MaxErrorCauseWalk)
196
+ return { levels, truncated: true };
197
+ const step = asRecord(current.step);
198
+ const name = nonEmptyText(step?.name);
199
+ const stepType = nonEmptyText(step?.type);
200
+ levels.push({
201
+ step: name ? `'${name}'${stepType ? ` (${stepType})` : ''}` : undefined,
202
+ code: nonEmptyText(current.code),
203
+ message: nonEmptyText(current.message),
204
+ detail: nonEmptyText(current.detail),
205
+ type: nonEmptyText(current.type)
206
+ });
207
+ current = asRecord(current.cause);
208
+ }
209
+ return { levels, truncated: false };
210
+ }
211
+ function seenKey(...parts) {
212
+ return JSON.stringify(parts);
213
+ }
214
+ /**
215
+ * Workflow 5xx gövdesinden, başlıkta zaten yazılmış `code`/`message` dışında kalan bilgiyi çıkarır.
216
+ * İki gövde şekli okunur: platformun `WorkflowStepErrorResponse`'u `{ code, message, detail, type }`
217
+ * (hata yolu olmayan akış, `ExceptionHandler.cs`) ve gateway hata adımının gövdesi
218
+ * `{ …, step: { name, type }, cause }` (`errorPath.ts`). `cause` zinciri alt akış seviyelerini
219
+ * taşır; aynı kod+mesaj her seviyede tekrarlandığı için mesaj EN DERİNDEN başlanarak bir kez, hatanın
220
+ * doğduğu adıma atfedilerek yazılır. Kodla aynı `type` ve mesajla aynı `detail` gürültüdür. Uzun
221
+ * zincirde yolun başı ve SONU gösterilir (hatanın doğduğu adım kaybolmasın); metinler kırpılır.
222
+ */
223
+ export function describeErrorBody(body, shown) {
224
+ const { levels, truncated } = errorLevels(body);
225
+ const fragments = [];
226
+ const path = levels.map(level => level.step).filter((step) => step !== undefined);
227
+ const shownPath = path.length > MaxErrorPathShown ? [...path.slice(0, 2), '…', ...path.slice(-3)] : path;
228
+ if (shownPath.length > 0)
229
+ fragments.push(`hata yolu: ${shownPath.join(' → ')}${truncated ? ' → …' : ''}`);
230
+ const seen = new Set();
231
+ if (shown) {
232
+ seen.add(seenKey('head', shown.code, shown.message));
233
+ if (levels[0])
234
+ seen.add(seenKey('head', levels[0].code, levels[0].message));
235
+ }
236
+ for (const level of [...levels].reverse()) {
237
+ const key = seenKey('head', level.code, level.message);
238
+ if ((level.code || level.message) && !seen.has(key)) {
239
+ seen.add(key);
240
+ fragments.push(`${level.step ? `${level.step} ` : ''}${[level.code, level.message].filter(Boolean).join(': ')}`);
241
+ }
242
+ if (level.detail && level.detail !== level.message && !seen.has(seenKey('detail', level.detail))) {
243
+ seen.add(seenKey('detail', level.detail));
244
+ fragments.push(`detail: ${level.detail}`);
245
+ }
246
+ if (level.type && level.type !== level.code && !seen.has(seenKey('type', level.type))) {
247
+ seen.add(seenKey('type', level.type));
248
+ fragments.push(`type: ${level.type}`);
249
+ }
250
+ }
251
+ return fragments;
252
+ }
180
253
  export function mapPlatformError(status, payload, context) {
181
254
  const body = payload && typeof payload === 'object' ? payload : {};
182
255
  const code = typeof body.code === 'string' ? body.code : undefined;
@@ -222,7 +295,9 @@ export function mapPlatformError(status, payload, context) {
222
295
  }
223
296
  const fallbackValidationMessages = collectValidationMessages(body);
224
297
  const fallbackDetail = fallbackValidationMessages.length > 0 ? ` → ${fallbackValidationMessages.join(' ; ')}` : '';
225
- return new PlatformApiError(`Platform hatası ${status} (${location}): ${apiMessage ?? 'beklenmeyen hata'}${code ? ` [${code}]` : ''}${fallbackDetail}${diagnoseKnownFailure(status, apiMessage ?? '', code, context)}`, status, code);
298
+ const errorBody = status >= 500 ? describeErrorBody(body, { code, message: apiMessage }) : [];
299
+ const errorBodyDetail = errorBody.length > 0 ? ` — ${errorBody.join(' · ')}` : '';
300
+ return new PlatformApiError(`Platform hatası ${status} (${location}): ${apiMessage ?? 'beklenmeyen hata'}${code ? ` [${code}]` : ''}${errorBodyDetail}${fallbackDetail}${diagnoseKnownFailure(status, apiMessage ?? '', code, context)}`, status, code);
226
301
  }
227
302
  export function mapNetworkError(error, context) {
228
303
  const cause = error instanceof Error ? error : new Error(String(error));
@@ -1,5 +1,5 @@
1
1
  import { logError, logStep } from '../observability/trace.js';
2
- import { PlatformApiError } from './errors.js';
2
+ import { PlatformApiError, describeErrorBody } from './errors.js';
3
3
  import { PolicyBlockError } from '../knowledge/policy.js';
4
4
  import { maskSecrets } from './secrets.js';
5
5
  export function prune(value) {
@@ -76,6 +76,17 @@ export function runtimeErrorSummary(payload) {
76
76
  const errors = [];
77
77
  if (typeof record.httpStatus === 'number' && record.httpStatus >= 400) {
78
78
  errors.push(`httpStatus: ${record.httpStatus} — çağrı ${record.httpStatus} ile bitti; gövde başarı çıktısı değil hata gövdesidir. Adım adım bakmak için microservice_debug kullanın.`);
79
+ // Yalnız 5xx: 4xx gövdeleri (422 doğrulama, 401) bilinçli iş yanıtıdır ve zaten gövdede okunur.
80
+ // `/debug/` ucu gövdeyi her zaman, `/m/` ve servis önizlemesi ise uiActions kurulmuşsa
81
+ // `{ data, uiActions[, executionItems] }` ile sarar (MicroservicesController.Debug / HandleResult).
82
+ const raw = record.body && typeof record.body === 'object' && !Array.isArray(record.body) ? record.body : undefined;
83
+ const wrapped = raw !== undefined && ('executionItems' in raw || 'uiActions' in raw) && raw.data && typeof raw.data === 'object' && !Array.isArray(raw.data);
84
+ const errorBody = wrapped ? raw.data : raw;
85
+ const describable = record.httpStatus >= 500 && errorBody !== undefined &&
86
+ (typeof errorBody.code === 'string' || Boolean(errorBody.step && typeof errorBody.step === 'object') || Boolean(errorBody.cause && typeof errorBody.cause === 'object'));
87
+ const fragments = describable ? describeErrorBody(errorBody) : [];
88
+ if (fragments.length > 0)
89
+ errors.push(`hata gövdesi — ${fragments.join(' · ')}`);
79
90
  }
80
91
  const stepErrors = [];
81
92
  collectStepErrors(body.steps, stepErrors);
@@ -0,0 +1,157 @@
1
+ {
2
+ "_meta": {
3
+ "synthetic": false,
4
+ "note": "Figma dosyasından 2026-09-16 tek geçiş protokolüyle okunan gerçek anlık görüntü: 8 çağrı (get_metadata sayfa listesi · get_variable_defs iki frame · get_metadata Deals frame ağacı · get_screenshot Deals · get_design_context buton ve durum rozeti, excludeScreenshot) ≈ 29 bin karakter. Kit iki token dili taşır (Fonts/*, Light/Basic/*); hints ajanın kararıdır ve dosyada kalır — ikinci oturum Figma'ya dönmez. Gölge (effects) okunmadı, alan yok.",
5
+ "usage": "Gövde <themeDiscoverArgs.snapshot> theme_discover'a verilir; öneri theme_update({ theme, dryRun: true }) ile önizlenir, sonra yazılır. Dosyanın yeri: zeyrek-mcp/docs/<koşu>/design/<fileKey>.snapshot.json."
6
+ },
7
+ "_rules": [
8
+ "Bütçe: tasarım dosyası başına ≤ 8 Figma çağrısı, bağlama giren ≤ 150 bin karakter; get_design_context yalnız atomik bileşene (excludeScreenshot: true), ekran frame'ine asla.",
9
+ "variables get_variable_defs çıktısının kendisidir — ad yeniden yazılmaz, değer olduğu gibi taşınır (Font(...) değerleri metin stili olarak okunur); eşleme adı okur.",
10
+ "Eşlenmeyen token için Figma'ya dönülmez: karar hints'e yazılır (tema yolu ya da ignore). Sayfa düzeyinde get_variable_defs seçim ister — frame id verin.",
11
+ "Koyu palet aydınlıktan türetilmez: variablesDark yoksa paletteDark boşluk kalır (THEME-009); isteğe bağlı deriveDark: true başlangıç noktası üretir."
12
+ ],
13
+ "themeDiscoverArgs": {
14
+ "snapshot": {
15
+ "source": {
16
+ "kind": "figma",
17
+ "fileKey": "9NnUskJSeNkMy1gAU2fAaA",
18
+ "fileName": "Recurring | Saas Company Management System (Community) (Copy)",
19
+ "nodeIds": [
20
+ "0:1",
21
+ "92:4339",
22
+ "79:8715",
23
+ "92:4343",
24
+ "92:5596"
25
+ ],
26
+ "fetchedAt": "2026-09-16",
27
+ "calls": 8,
28
+ "characters": 29000
29
+ },
30
+ "variables": {
31
+ "Primary": "#4787FA",
32
+ "Fonts/Primary": "#25213B",
33
+ "Fonts/Primary Variant": "#6E6893",
34
+ "Fonts/Primary Icon": "#8B83BA",
35
+ "Fonts/Positive": "#007F00",
36
+ "Backgrounds/Stripe": "#F4F2FF",
37
+ "White Primary": "#F3F4F8",
38
+ "Table/Footer": "Font(family: \"Inter\", style: Semi Bold, size: 12, weight: 600, lineHeight: 100, letterSpacing: 5)",
39
+ "Light/Basic/Primary": "#242731",
40
+ "Light/Basic/Secondary": "#575F6E",
41
+ "Light/Basic/Thriary": "#79808F",
42
+ "Light/Basic/Strokes": "#D4D4D4",
43
+ "Light/Basic/White": "#FFFFFF",
44
+ "Light/Accent/Primary": "#4277FF",
45
+ "Light/Background/Secondary": "#E3EBFF",
46
+ "Light/Background/Contrast": "#242731",
47
+ "Light/Invertion/Primary": "#FFFFFF"
48
+ },
49
+ "textStyles": [
50
+ {
51
+ "name": "Button label",
52
+ "fontFamily": "DM Sans",
53
+ "fontSize": 14,
54
+ "fontWeight": 700
55
+ },
56
+ {
57
+ "name": "Status pill",
58
+ "fontFamily": "Inter",
59
+ "fontSize": 12,
60
+ "fontWeight": 500
61
+ }
62
+ ],
63
+ "layout": {
64
+ "desktopWidth": 1440,
65
+ "mobileWidth": 375,
66
+ "sidebarWidth": 94,
67
+ "notes": [
68
+ "Kabuk (get_metadata 92:4339): solda 94 px 'Admin Nav' rayı, üstte 76 px 'Admin Top Nav'; içerik x=94'ten başlar, 1345 px genişlik",
69
+ "Liste (Deals): 'Headers' şeridi 76 px — solda 39 px yüksekliğinde başlık metni, sağda hap biçimli 202×46 px birincil buton; altında 48 px sekme şeridi (All · Paid · Unpaid · Overdue, aktif sekmenin altında 2 px çizgi) ve sağda toplam tutar; 45 px tablo başlığı (büyük harf); 61 px satırlar; satırda durum pill'i (19 px, önünde 6 px nokta), 'View More' ikincil buton 71×25, satır sonu 'Shape/More'; altta 53 px Pagination",
70
+ "Form (Create Client Modal 79:8715) iki token dili taşıyor: 'Light/Basic/*' metin tonları, 'Light/Accent/Primary' #4277FF (Deals'in #4787FA'sından farklı ikinci bir accent) — kit iki tasarım sisteminin karışımı",
71
+ "Masaüstü ızgarası iki nesil: eski ekranlar 1440×1024, yeni İK ekranları 1728×1117 — 1440 taban alındı"
72
+ ]
73
+ },
74
+ "components": {
75
+ "button/primary": {
76
+ "background": "#4787FA",
77
+ "color": "#FFFFFF",
78
+ "borderRadius": 23,
79
+ "height": 46,
80
+ "paddingX": 29,
81
+ "paddingY": 14,
82
+ "fontSize": 14,
83
+ "fontWeight": 700
84
+ },
85
+ "badge/status": {
86
+ "background": "#E6E6F2",
87
+ "color": "#4A4AFF",
88
+ "borderRadius": 10,
89
+ "height": 19,
90
+ "paddingX": 18,
91
+ "paddingY": 2,
92
+ "fontSize": 12,
93
+ "fontWeight": 500
94
+ }
95
+ },
96
+ "screens": [
97
+ {
98
+ "nodeId": "92:4339",
99
+ "name": "Deals",
100
+ "role": "list",
101
+ "notes": [
102
+ "sekme filtreleri + toplam tutar vurgusu + pill rozetler; 11 satır + Pagination"
103
+ ]
104
+ },
105
+ {
106
+ "nodeId": "74:4098",
107
+ "name": "Clients",
108
+ "role": "list"
109
+ },
110
+ {
111
+ "nodeId": "79:8715",
112
+ "name": "Create Client Modal",
113
+ "role": "form"
114
+ },
115
+ {
116
+ "nodeId": "310:5445",
117
+ "name": "grant leave",
118
+ "role": "form"
119
+ },
120
+ {
121
+ "nodeId": "310:3809",
122
+ "name": "employee profile",
123
+ "role": "detail"
124
+ },
125
+ {
126
+ "nodeId": "230:3757",
127
+ "name": "Login",
128
+ "role": "auth",
129
+ "notes": [
130
+ "767 px mobil varyantı var"
131
+ ]
132
+ },
133
+ {
134
+ "nodeId": "8:3389",
135
+ "name": "settings",
136
+ "role": "other"
137
+ }
138
+ ],
139
+ "hints": {
140
+ "Light/Accent/Primary": "ignore",
141
+ "Light/Basic/Primary": "ignore",
142
+ "Light/Basic/Secondary": "ignore",
143
+ "Fonts/Primary Variant": "text.secondary",
144
+ "Fonts/Primary Icon": "ignore",
145
+ "Fonts/Positive": "status.success",
146
+ "Backgrounds/Stripe": "background.surfaceAlt",
147
+ "White Primary": "background.canvas",
148
+ "Light/Basic/Thriary": "text.disabled",
149
+ "Light/Basic/Strokes": "border.primary",
150
+ "Light/Basic/White": "background.surface",
151
+ "Light/Background/Secondary": "accent.subtle",
152
+ "Light/Background/Contrast": "background.inverse",
153
+ "Light/Invertion/Primary": "accent.onPrimary"
154
+ }
155
+ }
156
+ }
157
+ }
@@ -18,7 +18,7 @@ yazmalarının aksine burada "önce development'ta dene" diye bir ayrım YOKTUR.
18
18
  | Alan | Ne yapar | Ürettiği değişken(ler) |
19
19
  |---|---|---|
20
20
  | `mode` | `'light'` (varsayılan) \| `'dark'` — yayınlanan uygulamanın modu | palet tabanını seçer |
21
- | `palette` | AYDINLIK moddaki renk override'ları | `--ng-accent-*` · `--ng-background-*` · `--ng-text-*` · `--ng-border-*` · `--ng-status-*` |
21
+ | `palette` | AYDINLIK moddaki renk override'ları; `accent.onPrimary` isteğe bağlıdır — accent ÜSTÜNDEKİ metin, verilmezse kontrasttan türer (`#4787FA` gibi açık bir mavi siyah metin üretir, tasarımda beyazsa buraya yazın) | `--ng-accent-*` · `--ng-background-*` · `--ng-text-*` · `--ng-border-*` · `--ng-status-*` · `--ng-text-on-accent` |
22
22
  | `paletteDark` | KARANLIK moddaki renk override'ları | aynı küme |
23
23
  | `fontFamily` · `headingFontFamily` | Gövde ve başlık yazı tipi (Google Fonts adı) | `--ng-font` · `--ng-font-heading` |
24
24
  | `fontSize` · `fontWeight` | Taban punto ve ağırlık | `--ng-font-size` · `--ng-font-weight` |
@@ -208,3 +208,86 @@ kareyi de alın: `page_screenshot(…)` ve `page_screenshot(…, colorScheme: 'd
208
208
  **Renkler hex olmalı.** CSS `rebeccapurple`'ı çizer ama studio'nun renk alanları hex bekler
209
209
  ve vurgu üstündeki metin rengi (`--ng-text-on-accent`) accent'i karakter karakter okuyup
210
210
  kontrast hesapladığı için tanımadığı değerde beyaza düşer (THEME-005).
211
+
212
+ ## Tasarım dosyasından tema (Figma) — tek geçiş
213
+
214
+ Bir Figma dosyası **bir kez** okunur, makine-okur bir **anlık görüntüye** alınır ve tema o
215
+ dosyadan türetilir. Sonrası Figma'sızdır: sayfa kurma, preset bağlama ve koyu mod anlık görüntü +
216
+ `theme_get` ile yürür. Ölçüm (2026-09-16): ekran frame'lerine atılan altı `get_design_context`
217
+ çağrısı yaklaşık **bir milyon karakter** React kodu getirdi; kullanılan yalnız renk, boşluk, yazı
218
+ tipi ve düzendi. O bilgi Figma'nın ucuz tool'larından çıkar.
219
+
220
+ **Bütçe: tasarım dosyası başına ≤ 8 Figma çağrısı, bağlama giren ≤ 150 bin karakter.**
221
+ İş tanımına frame URL'lerini yazdırın — sayfa düzeyinde ağaç dökümü tek başına 1 MB'tır.
222
+
223
+ | Adım | Figma çağrısı | Bütçe | Anlık görüntüye ne girer |
224
+ |---|---|---|---|
225
+ | 1 | `get_metadata(fileKey)` — `nodeId` YOK | 1 çağrı, < 2 K | `source.fileName`, sayfa id'si |
226
+ | 2 | **Koşullu:** insan frame URL'si vermediyse `get_metadata(fileKey, nodeId: <sayfa>)`; yalnız üst seviye frame'lerin ad + id'si okunur | 1 çağrı; büyükse harness dosyaya alır | `screens[]` (`role`'ü siz atarsınız: shell · list · form · detail · auth · other) |
227
+ | 3 | `get_variable_defs(fileKey, nodeId: <kütüphane/Components frame'i, yoksa ana ekran>)`; koyu mod frame'i varsa ona da. **Sayfa id'si (`0:1`) çalışmaz** — "select a layer" hatası; her zaman frame id verin. Farklı frame'ler farklı token dili taşıyabilir (ölçüldü: `Fonts/*` ve `Light/Basic/*` aynı dosyada) — iki temsilci frame yeter | 1–2 çağrı, 0,5–10 K | `variables`, `variablesDark` — **olduğu gibi**, ad yeniden yazılmaz; `Font(family: …, size: …, weight: …)` değerleri metin stili olarak okunur |
228
+ | 4 | `get_screenshot(fileKey, nodeId, maxDimension: 800)` — liste, form, detay için birer | ≤ 3 çağrı; varsayılan URL kipi, `curl -o` ile diske | `screens[].screenshot` (yerel yol), `layout.notes` (gözlem: ray genişliği, ızgara, kart/pill dili) |
229
+ | 5 | `get_design_context(fileKey, nodeId: <ATOMİK bileşen>, excludeScreenshot: true)` — button, badge, input; **ekran frame'ine ASLA** | ≤ 2 çağrı | `components` (ölçüler), `textStyles`, `effects` |
230
+
231
+ Tip ölçeği için sıra: (a) `variables` içinde `font/size/h1` gibi adlar, (b) kütüphanede bir
232
+ "Typography" bileşeni varsa 5. adımın bir çağrısı ona, (c) yoksa alan boş kalır ve tema önerisi
233
+ bunu `gaps` ile söyler — ekran frame'i bunun için de açılmaz.
234
+
235
+ **Anlık görüntü sözleşmesi** (`design-snapshot` altın örneği; her seviyede strict, tanınmayan
236
+ anahtar reddedilir):
237
+
238
+ | Alan | Ne |
239
+ |---|---|
240
+ | `source` | `kind` (`figma` \| `manual`), `fileKey`, `fileName`, `nodeIds`, `fetchedAt`, ölçüm için `calls` ve `characters` |
241
+ | `variables` · `variablesDark` | `get_variable_defs` çıktısı: `{ 'Primary': '#4787FA', 'spacing/md': 16 }`; koyu küme ayrı |
242
+ | `textStyles[]` | `name`, isteğe bağlı `role` (h1…small), `fontFamily`, `fontSize` (px), `fontWeight`, `lineHeight` |
243
+ | `effects[]` | gölge: `name`, offset/blur/spread, `color`, `opacity` (Figma'nın 0–1'i) |
244
+ | `layout` | `desktopWidth`, `mobileWidth`, `gutter`, `sidebarWidth`, `notes[]` — temaya gitmez, sayfa fazı için saklanır |
245
+ | `components` | `'button/primary'` → `background`, `color`, `border`, `borderRadius`, `paddingX/Y`, `height`, `fontSize`, `fontWeight`, `shadow` |
246
+ | `screens[]` | `nodeId`, `name`, `role`, `screenshot` (yerel yol), `notes[]` |
247
+ | `hints` | Figma adı → tema yolu (`accent.muted`, `colors.blue.500`) ya da `ignore` — **sizin kararınız, kalıcı** |
248
+
249
+ Dosya koşu klasöründe yaşar: `zeyrek-mcp/docs/<koşu>/design/<fileKey>.snapshot.json`. Ekran
250
+ görüntüleri repoya girmez. Gateway bu dosyayı hatırlamaz (HTTP taşımasında her çağrı yeni
251
+ sunucudur); ikinci oturum dosyayı okur, Figma'ya dönmez.
252
+
253
+ **Eksik değer için Figma'ya dönülmez.** Öneri bir token'ı eşleyemediyse sebebini `unmapped`'da
254
+ söyler; kararı `hints`'e yazıp yeniden çevirirsiniz. Değer dosyada hiç yoksa (`gaps`) ekran
255
+ görüntüsünden ya da insandan alıp anlık görüntüye elle eklersiniz (`source.kind` `figma` kalır,
256
+ `notes` söyler). Koyu palet aydınlıktan türetilmez: `variablesDark` yoksa `paletteDark` boşluktur
257
+ (THEME-009 uyarır).
258
+
259
+ Akış: anlık görüntüyü yaz → `theme_discover({ snapshot })` → `unmapped`/`gaps`'e bak, `hints` yaz →
260
+ `theme_discover` yeniden (`project` verirseniz mevcut temaya göre `cssVarsChanged` önizlemesi de
261
+ gelir) → `theme_update({ theme, dryRun: true })` → `theme_update({ theme })` →
262
+ `theme_get(view:'resolved')` → `page_screenshot` (aydınlık + `colorScheme:'dark'`). Bu rehberin
263
+ §Doğrulama sırası aynen geçerlidir.
264
+
265
+ `theme_discover` yazmaz ve tahmin etmez: öneri `theme_update` şemasındadır, `mapping` her Figma
266
+ adının hangi yola hangi kuralla (`role` · `ramp` · `hint` · `textStyle` · `variable` · `effect` ·
267
+ `component` · `component→var`) gittiğini söyler; yalnız SAPAN değer yazılır (platform varsayılanına
268
+ eşit taban ve ±0,05 içindeki oran öneriye girmez). Tek istisna `typeScale.p: 1` — platformda p
269
+ tabanın 0,925'idir, Figma gövdesi tabanın kendisi olduğu için yazılmazsa 15 px gövde 13,9 px
270
+ çizilir. Grup kelimesinin yanında tanınmayan bir niteleyici (`bg/error`, `text/on-primary`,
271
+ `button/primary`) varsayılana DÜŞMEZ, `unmapped` olur — kararı `hints` ile verirsiniz. Bileşen
272
+ ölçüleri adlı preset olur ve eşlenmiş renkler `var(--ng-*)` ile bağlanır (preset temayı izler);
273
+ `componentPresets` üretilmez. Gateway'in düzelttiği değerler (hex büyük harf, `rgba` → hex, gölge
274
+ `opacity` ×100, yazı tipi yazımı) yanıtta `gatewayNormalized` ile listelenir.
275
+
276
+ Bileşen adının ilk kelimesi katalogdaki bir TİP ise (`button/primary` → `button`) aynı ölçüler o
277
+ tipin **bileşen preseti** (`componentPresets.button.versions.V1.props.style`) olarak da önerilir:
278
+ adlı preset mevcut düğümlere bağlanır, bileşen preseti yeni düğümlere başlangıç olur. `input` katalog
279
+ tipi değildir ve `textinput` diye düzeltilmez — adı katalog tipiyle başlatın (`component_list`).
280
+ Koyu palet varsayılan olarak türetilmez; `theme_discover({ snapshot, deriveDark: true })` Figma'da
281
+ koyu küme YOKSA accent/status değerlerinden deterministik bir başlangıç noktası üretir ve her değeri
282
+ `via: 'derived'` ile işaretler — marka kararı değil, koyu kareyi görüp düzeltmek sizin işiniz.
283
+
284
+ **Ekranlar için `page_discover`.** Anlık görüntüdeki bir ekranın rolünden (list · form · detail ·
285
+ auth · shell) altın örnek arketipini seçip `page_create` + `page_schema_patch` iskeletini döner;
286
+ `theme_discover`'ın ürettiği adlı preset tek adaysa bileşene `props.style.$preset` olarak bağlanır.
287
+ Piksel kopyası değildir: `caveats` neyin doldurulacağını, `next` çağrı sırasını söyler.
288
+
289
+ `hints` biçimleri: çıplak `accent.hover` **iki kümeye de** uygulanır (Figma modları aynı adı
290
+ aydınlık ve koyu değerle verir — `variables`'taki `palette`'e, `variablesDark`'taki
291
+ `paletteDark`'a gider); `palette.accent.hover` / `paletteDark.accent.hover` yalnız kendi kümesinde;
292
+ `colors.blue.500` rampaya (yalnız aydınlık); `ignore` hiç eşleme. Anlık görüntüde olmayan bir ada
293
+ hint yazmak hatadır — Figma'da ad değiştiyse hint de değişir.
@@ -227,19 +227,68 @@ tanımı kendi global-error dalınla gönder — dal **bağlıysa** (`nextStepId
227
227
  dokunmaz. `nextStepId: null` ile bağlanmamış gönderirsen gateway ikinci bir dal eklemez, mevcut
228
228
  dalını enjekte ettiği hata adımına bağlar.
229
229
 
230
- **Fail dallarını tek tek bağlamana gerek yok.** Bir adımın `fail` dalı bağlanmamışsa
231
- (`nextStepId: null`) ya da hiç yoksa, hata **kendiliğinden** global-error dalına gider. `fail`
232
- dalını yalnız o adıma özel bir telafi akışı (örn. "kayıt yoksa oluştur") istiyorsan bağla.
230
+ ### `gatewayOnError`'un 500 gövdesi: patlayan adım ve alt akış zinciri (2026-09-16)
231
+
232
+ ```jsonc
233
+ // POST /m/web/ticket-ac → 500 (ebeveyn → callChild → çocuk akış → childBoom patladı)
234
+ { "code": "JavaScriptException", "message": "Error: x is not defined\n at <anonymous>:1:8",
235
+ "detail": "…", "type": "JavaScriptException",
236
+ "step": { "name": "callChild", "type": "workflow" }, // bu akışta patlayan adım
237
+ "cause": { "code": "JavaScriptException", "message": "…", // alt akışın KENDİ hata gövdesi
238
+ "step": { "name": "childBoom", "type": "code" } } } // çocukta patlayan adım
239
+ ```
240
+
241
+ - **`step` yalnız `{ name, type }`'tır.** `error.step.input` ifade değerlerini (parola dahil),
242
+ `error.step.data` adımın çıktısını taşır; ikisi de dışarı yazılmaz. `error.step`'i bütün olarak
243
+ gövdeye koyarsan platform onu **siler** (`ResponseStep` global-error kipinde `error.step` ile
244
+ birebir aynı `step` alanını kaldırır) — alt küme yaz.
245
+ - **`cause` bir seviye aşağıyı taşır** ve her seviyede yalnız `code`, `message`, `detail`, `type`,
246
+ `step { name, type }` ve `cause` geçer (gateway beyaz listeye süzer, en fazla dokuz seviye). Backend
247
+ 2026-09-17'den beri zinciri `error.cause`'ta kendisi kurar; üç durum kapsanır:
248
+ - **Alt akış patladı:** `cause.step` çocukta patlayan adım, çocuk da alt akış çağırıyorsa zincir
249
+ seviye seviye iç içe iner. Çocuk Studio'da hata yolsuz kurulduysa da adım adı gelir.
250
+ - **Loop iterasyonu patladı:** `step` loop adımı, `cause.step.name` `loopRows[0]` gibi
251
+ `<loop adı>[<sıra>]`, iterasyondaki alt akışın zinciri onun altında.
252
+ - **Çocuk istisnasız 2xx dışı döndü** (`response` 422): `code` `SubWorkflowFailedException`,
253
+ `message` `Sub-workflow '<ad>' returned 422 (<gövde code>): <gövde message>`.
254
+
255
+ Eski backend'de `error.cause` yoktur; gateway o zaman patlayan `workflow` adımının `step.data`'sına
256
+ (çocuğun kendi gövdesi) düşer — loop ve istisnasız çocuk orada kaybolur. Çocuk kendi 4xx/5xx yanıtını
257
+ elle yazdıysa (`({ success: false, token })`, `({ err: error })`) o gövdenin yalnız bu alanları geçer.
258
+ Canlı ölçüldü (2026-09-16/17): torunun `token` ve `step.input`'u üst gövdeye ulaşmadı; üç seviyeli
259
+ zincir ve `loopRows` → `loopRows[0]` → `boomStep` hem hata yolsuz hem gateway hata yollu uçta döndü.
260
+ - **Gateway zinciri okur (yalnız 5xx):** `microservice_execute`/`debug`/`preview` `runtimeErrors`'a
261
+ `hata gövdesi — hata yolu: 'callChild' (workflow) → 'childBoom' (code) · 'childBoom' (code)
262
+ JavaScriptException: …` satırını ekler (debug ucunun `{ data, executionItems }` sarmalı açılır);
263
+ fırlatan çağrıların (senkron automation'lı `row_create`, `workflow_preview`…) araç hatası da
264
+ gövdede bulunan `detail`'i, koddan farklı `type`'ı ve varsa zinciri yazar. 4xx gövdeleri
265
+ süslenmez — onlar bilinçli iş yanıtıdır.
266
+ - **Eski kayıtlar:** 2026-09-16 öncesi gateway `({ code: error.code, message: error.message })`
267
+ yazıyordu. Global-error dalının hedefi olan, adı `gatewayOnError[n]`, ifadesi bu metinle AYNEN
268
+ aynı ve açıklaması gateway'in eski metni (ya da boş) olan adım bir sonraki
269
+ `workflow_create`/`update`/`patch`/`preview`'da yeni gövdeye yükseltilir (`gatewayNormalized`:
270
+ "hata gövdesi genişletildi"; önizleme kaydetmez). Yanıta alan eklenir, `code`/`message` değişmez.
271
+ Açıklaması ya da ifadesi elle değiştirilmiş adıma dokunulmaz; yükseltme açıklamayı da yenilediği
272
+ için sonradan bilerek eski gövdeye dönen `bodyExpression` bir daha ezilmez.
273
+ - **Dışarı ne verdiğine sen karar verirsin.** Adım adları iç yapıyı, `detail` iç istisna mesajını
274
+ (kısıt adı, host) gösterebilir. **Middleware'in 500 gövdesi korumalı uçların anonim çağıranına da
275
+ gider** (`ExecuteMicroserviceBehavior`). Herkese açık yüzeyde istemiyorsan hata adımının
276
+ `bodyExpression`'ını daralt, örn. `({ code: error.code, message: error.message })`.
233
277
 
234
278
  Global-error dalında `error` değişkeni tanımlıdır:
235
279
 
236
280
  ```jsonc
237
281
  { "code": "RowAlreadyExists", "message": "…", "detail": "…", "type": "…",
238
- "step": { "id": "…", "name": "writeRow", "type": "table", "data": …, "input": [ … ] } }
282
+ "step": { "id": "…", "name": "writeRow", "type": "table", "data": …, "input": [ … ] },
283
+ "cause": null } // alt akış / loop iterasyonunda { code, message, detail, type, step: { name, type }, cause }
239
284
  ```
240
285
 
241
- `error.step` hangi adımın patladığını söyler — hata yanıtına koymadan önce iki kez düşün, iç
242
- yapıyı dışarı sızdırır.
286
+ Fail dalında aynı zincir `<adım adı>.error.cause`'tadır (`<adım adı>.error` = `{ code, message, detail,
287
+ type, cause }`).
288
+
289
+ `error.step` hangi adımın patladığını söyler. `input`/`data` alanları ifade değerlerini ve adım
290
+ çıktısını taşır — gövdeye yalnız ihtiyaç duyduğun alt kümeyi yaz (gateway'in varsayılanı
291
+ `{ name, type }`'tır, yukarıdaki §`gatewayOnError`'un 500 gövdesi).
243
292
 
244
293
  **Kurallar ve sınırlar:**
245
294
 
@@ -257,9 +306,14 @@ yapıyı dışarı sızdırır.
257
306
  bağlı — §izin matrisi) ya da `email`/`table` adımına bağla. Bağlı hata yolu yoksa `WF-021` uyarır.
258
307
  - **Hata yolu hiç yoksa** (fail dalı ve global-error dalı bağlı değil) adım patladığında motor
259
308
  canlıda `WorkflowStepFailedException` fırlatır (2026-09-16): HTTP yanıt 500 +
260
- `{ code, message, detail, type }` (patlayan adımın hatası), zamanlanmış iş ve automation geçmişi
261
- `failed` + `Step 'x' failed: <sebep>`, senkron automation tetikleyen satır yazımını düşürür.
262
- Önizlemede fırlatmaz, ağaç döner. Çalışır, ama çıktı lokalize değildir.
309
+ `{ code, message, detail, type, step: { name, type }, cause }` — `step` ve `cause` 2026-09-17'den
310
+ beri, gateway hata adımıyla aynı şekil (yerel ortamda ayrıca `stackTrace`). Zamanlanmış iş ve
311
+ automation geçmişi `failed` + `Step 'x' failed: <sebep>`, senkron automation tetikleyen satır
312
+ yazımını düşürür. Önizlemede fırlatmaz, ağaç döner. Çalışır, ama çıktı lokalize değildir.
313
+ - **Adım hata yolunun DIŞINDA patlarsa** (ör. `revert`'in `revertType` ifadesi tırnaksız) kod
314
+ `EvaluateWorkflowStepFailedException`, mesaj `Step '<ad>' threw outside its error path: <sebep>`
315
+ olur: fail dalı ve global-error dalı ÇALIŞMAZ, canlı koşu düşer. 2026-09-17'den beri önizleme
316
+ ağacı kaybolmaz; adım `hasError: true` ile işaretlenir.
263
317
 
264
318
  > **Preview HTTP 200 dönerken bile akış patlamış olabilir.** `executionSuccess` yalnız SON adıma
265
319
  > bakar; ortada patlayıp hata dalından dönen akış `true` gösterir. Gateway tüm adımları (iç içe
@@ -347,6 +401,63 @@ gidilir, hiçbiri tutmazsa `else` dalı yürür:
347
401
  > `rule.isExpression` bayrağı bu iki alan için **okunmaz**.
348
402
 
349
403
 
404
+ ## Workflow adımı: başka bir akışı parametreyle çağırmak (WF-032)
405
+
406
+ Ortak mantığı (kayıt açma, bildirim, numaralandırma) **bir kez** `type:'workflow'` akışı olarak
407
+ yazıp diğer akışlardan çağırmanın yolu budur — 30 adımı kopyalamak değil. Adım tipi `workflow`;
408
+ üç akış tipinde de kullanılabilir. Kuralı **iki alan** taşır ve girdi eşlemesi ikincisidir:
409
+
410
+ ```jsonc
411
+ { "id": "<GUID>", "name": "createTicket", "type": "workflow",
412
+ "description": "Telemetri olayından create-ticket akışıyla kayıt açar.",
413
+ "rule": {
414
+ "workflowId": "<create-ticket akışının id'si>",
415
+ "expression": "({ title: input.title, source: 'telemetry', payload: parseEvent.data })"
416
+ },
417
+ "branches": [
418
+ { "id": "<GUID>", "name": "success", "type": "success", "nextStepId": "<sonraki>" },
419
+ { "id": "<GUID>", "name": "fail", "type": "fail", "nextStepId": "<hata adımı>" }
420
+ ] }
421
+ ```
422
+
423
+ - **Eşleme `rule.expression`'daki nesne literalidir: her anahtar çağrılan akışın parametre
424
+ DTO'sundaki bir alan, her değer bir JS ifadesi** (`input.x`, önceki adımın `<ad>.data.x`'i,
425
+ sabit). Studio'nun adım editörü de aynı şeyi yapar — akış seçilince ifadeyi `parameterScheme`'den
426
+ `{ "alan": <örnek> }` olarak doldurur (`WorkflowTrigger.svelte`). Table adımının `fields[]`
427
+ listesinin buradaki karşılığı budur; ayrı bir liste alanı **yoktur** ve gerekmez.
428
+ - **Çağrılan akışın `parameterDtoId` + `parameterBehavior`'ı OLMAK ZORUNDA.** `WorkflowStep`
429
+ ifadeyi yalnız `workflow.HasParameter` iken değerlendirir (`WorkflowStep.cs`); DTO'suz alt
430
+ akışa yazılan ifade hiç koşmaz, alt akış boş `input` ile çalışır ve **200 döner**. Gateway
431
+ çağrılan akışı okuyup bunu **block** eder (WF-032). Reçete: `dto_create` → `workflow_patch(id:
432
+ <alt akış>, ops: [{ op: 'add', path: '/parameterDtoId', value: <dtoId> }, { op: 'add', path:
433
+ '/parameterBehavior', value: 'object' }])`.
434
+ - **Başka hiçbir alan girdi taşımaz.** Şema üyeleri (`payloadExpression`, `customParameters`,
435
+ `queryParameters`, `bodyExpression`, `fields`) kaydedilir ama `WorkflowStep` okumaz; şema dışı
436
+ adlar (`parameters`, `input`, `payload`, `args`…) platformda hiç kaydedilmez (`unmappedMembers`).
437
+ İki durumda da alt akış boş girdi alır (RFQ koşusu W-7, 2026-09-14: beş biçim denendi, beşinde
438
+ de boş girdi). Gateway DOLU değer görürse block eder; `workflow_get`'in geri okuttuğu
439
+ `fields: []` / `customParameters: null` gibi boş varsayılanlar kuralı tetiklemez.
440
+ - **Ad tuzağı:** `rule.expression` `code` adımında GÖVDE (çalıştırılan kod), `workflow` ve
441
+ `microfunction` adımında GİRDİ (argüman nesnesi) demektir. Aynı ad, iki anlam.
442
+ - **Alt akış girdiyi `input.<alan>` ile okur ve onun `input`'u bu nesnedir** — çağıranın
443
+ `input`'unu görmez; çağıranın bir alanı gerekiyorsa eşlemeye koy. `parameterBehavior: 'array'`
444
+ ise ifade dizi üretmelidir (gateway nesne literali görürse uyarır).
445
+ - **Çıktı alt akışın yanıt gövdesidir** (`response` adımının `returnDtoId` süzgecinden geçmiş
446
+ hâli) ve sonraki adımlara `<adım adı>.data` ile akar. Alt akış 2xx dışı dönerse ya da bir
447
+ adımı düşerse çağıran adım **fail** dalına gider; `<adım adı>.data` o zaman alt akışın hata
448
+ gövdesidir.
449
+ - **Şekli tahmin etme, oku:** `workflow_step_schema(step)` çağrılan akışın `parameterScheme` ve
450
+ `returnScheme`'ini döner; ya da `workflow_get(<workflowId>)` → `parameterDtoId` → `dto_get`.
451
+ - **Loop içinde:** `rule.stepType: 'workflow'`, `rule.arrayExpression`, çocuk kural
452
+ `rule.stepRule: { workflowId, expression: "({ title: loopItem.title })" }` — WF-032 çocuğu da
453
+ denetler.
454
+ - **Önizlemede** alt akışın kendi adım izi `steps[].children[]` altında `kind: 'subWorkflow'` ile
455
+ gelir; çağıran adımın `input[]` listesinde `member: 'parameter'` satırı ifadenin ürettiği nesneyi
456
+ gösterir — **satır yoksa ya da değeri boşsa girdi bağlanmamıştır.** Alt akışı tek başına
457
+ sınamak için kendi `previewPayload`'ı ile `workflow_preview` çağır.
458
+ - Aynı alt akışı birden çok adımdan çağırmak serbesttir; özyineleme runner'ın koşum sayacına
459
+ takılır (kural envanteri WF-028 — gateway kuralı değil, backend davranışı).
460
+
350
461
  ## Loop step — dizinin her öğesi için bir adım koştur
351
462
 
352
463
  Tek uçta "başlık + N alt kayıt" yazmanın yolu budur (anket + sorular + seçenekler tek çağrıda;
@@ -2,7 +2,7 @@ import { guid, entityRef, resolveEntityId, withResolved, projectParam, environme
2
2
  import { RefResolver } from '../platform/refs.js';
3
3
  import { normalizeWorkflowDefinition, withNormalization } from '../knowledge/normalize.js';
4
4
  import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
5
- import { policyRules, microfunctionStepFindings } from '../knowledge/rules.js';
5
+ import { policyRules, microfunctionStepFindings, workflowStepFindings } from '../knowledge/rules.js';
6
6
  import { languageHeader } from '../knowledge/languageCodes.js';
7
7
  import { handle } from '../platform/responses.js';
8
8
  import { z } from 'zod';
@@ -40,7 +40,8 @@ export function registerAutomationTools(server, platform) {
40
40
  const normalized = normalizeWorkflowDefinition(definition);
41
41
  const findings = enforcePolicy([
42
42
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'create', workflowKind: 'automation' }, policyOverride),
43
- ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
43
+ ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride),
44
+ ...await workflowStepFindings(platform, project, environment, undefined, definition, 'automation', policyOverride)
44
45
  ]);
45
46
  const resolver = new RefResolver(platform, project, environment);
46
47
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
@@ -56,7 +57,8 @@ export function registerAutomationTools(server, platform) {
56
57
  const normalized = normalizeWorkflowDefinition(definition);
57
58
  const findings = enforcePolicy([
58
59
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'update', workflowKind: 'automation' }, policyOverride),
59
- ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
60
+ ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride),
61
+ ...await workflowStepFindings(platform, project, environment, undefined, definition, 'automation', policyOverride)
60
62
  ]);
61
63
  const resolver = new RefResolver(platform, project, environment);
62
64
  const resolvedEntityId = await resolveEntityId(resolver, entityId);
@@ -135,7 +137,8 @@ export function registerAutomationTools(server, platform) {
135
137
  const normalized = normalizeWorkflowDefinition(definition);
136
138
  const findings = enforcePolicy([
137
139
  ...evaluatePolicy(config, policyRules, 'workflow', definition, { operation: 'preview', workflowKind: 'automation' }, policyOverride),
138
- ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride)
140
+ ...await microfunctionStepFindings(platform, project, environment, definition, 'automation', policyOverride),
141
+ ...await workflowStepFindings(platform, project, environment, undefined, definition, 'automation', policyOverride)
139
142
  ]);
140
143
  const resolver = new RefResolver(platform, project, environment);
141
144
  const resolvedEntityId = await resolveEntityId(resolver, entityId);