@appaflytech/zeyrek-ai-mcp 0.0.53 → 0.0.54

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.
@@ -70,6 +70,11 @@ Studio'nun runtime rotasının üç şeridi vardır ve **ortamı URL belirler**:
70
70
  Sayfanın **bütün** istekleri o dala gider: sayfa şeması da, akışların uç çağrıları da. Yani kare
71
71
  "yarı doğru" değildir — gördüğünüz ekran dalın gerçek ekranıdır.
72
72
 
73
+ `{app}` **o ortamın** uygulama slug'ıdır: dal uygulama kaydını açıldığı andaki hâliyle tutar, production
74
+ dağıtıma kadar eskisini; `application_list` ise development slug'ını verir. Uygulama sonradan yeniden
75
+ adlandıysa Core slug'ı dalda/production'da 404 verir — `applicationSlug`'a uygulama **id**'sini verin,
76
+ gateway ortamın slug'ını okur; dal açıldıktan sonra kurulan uygulama dalda yoktur (APP-002).
77
+
73
78
  Reddedilen tek şey **dal adı olamayacak bir ad**: harfle başlamayan, küçük harf/rakam/alt çizgi/tire
74
79
  ve `/` ile ayrılmış parçalar dışında karakter taşıyan, 3-31 aralığının dışına çıkan ya da rezerve bir
75
80
  ad (`public`, `api`, `pg_*` …). Projedeki dalları ve slug'larını `environment_list` verir; tenant
@@ -96,8 +96,9 @@ bağlantısı) Studio › Yayınla'dan davet edilir — kişisel e-posta taşıd
96
96
  gönderim** (adım 8: `app-store-review`, `app-store-release`, `play-promote`) geri dönüşsüz ya da insan onaylıdır;
97
97
  ajan yalnız `dryRun` ile ön kontrolünü okur ve kullanıcıyı Studio'ya yönlendirir.
98
98
 
99
- **Ekran görüntüsü:** `page_screenshot { environment: 'production', device, format: 'jpeg', quality: 92,
100
- language, colorScheme: 'light', outPath }` → `mobile_asset_create { path, kind: 'screenshot', deviceClass, locale }`.
99
+ **Ekran görüntüsü:** `page_screenshot { environment: 'production', applicationSlug: <uygulama id'si>, device, format: 'jpeg', quality: 92,
100
+ language, colorScheme: 'light', outPath }` — id verilince gateway production'ın kendi slug'ını okur (yeniden adlanan
101
+ uygulama dağıtıma kadar eski slug'la açılır, APP-002) → `mobile_asset_create { path, kind: 'screenshot', deviceClass, locale }`.
101
102
  `device`: iphone69 `{440,956,3}` · ipad13 `{1032,1376,2}` · phone `{360,720,3}` · tablet7 `{600,960,2}` ·
102
103
  tablet10 `{800,1280,2}`. JPEG şart (iOS alfa kabul etmez); giriş/splash sayfası çekilmez (App Review 2.3.3).
103
104
  ikon/splash/öne çıkan görsel tekli yuvadır: yeni yükleme eskisinin yerine geçer.
@@ -596,7 +596,8 @@ yaptıkları. Derlenen uygulama
596
596
  (MOB-033), `Steps.<ad>.cache.at`, `error.network` — `platform_guide('page')` §Kural 1g-4. Önizleme önbellek kullanmaz.
597
597
  Yalnız GET okuma saklanır (POST okuma ağsız açılmaz → GET + query-string, FLOW-044); kuyruk değişimi sayfanın
598
598
  `offlineQueueChange` olayıdır, yoklama gerekmez (§Kural 1g-4).
599
- Ayar (`properties`, ana sayfa, bulunamadı sayfası) `application_update` ile yalnız **development**'a yazılır; production'da
600
- `deployment_start`'tan sonra geçerli olur (APP-001) — önce development'ta deneyin.
599
+ Uygulama kaydı (ad, slug, `properties`, ana sayfa, bulunamadı sayfası) `application_update` ile yalnız
600
+ **development**'a yazılır; production'da `deployment_start`'tan sonra geçerli olur (APP-001) — önce development'ta
601
+ deneyin. Slug değişirse production derlemesi dağıtıma kadar eski slug'ı taşır.
601
602
  "Uygulama ağsızken de çalışsın" isteğinin tarifi (okuma GET, yazma kuyruğu, liste birleştirme, düğme kilidi, korumasız
602
603
  açılış, bant kapalı): §Kural 1g-4 "Ağsızken fark göstermeyen uygulama", örnek `ngapps://examples/mobile-offline-seamless`.
@@ -54,8 +54,9 @@ başlamadan önce §Yolculuk, §Test derlemesi, §Akış, §Yaş derecelendirmes
54
54
  `adaptiveMonochrome`, `splash`) eksikse kullanıcıdan dosya iste ya da var olan dosyanın yolunu sor.
55
55
  Ekran görüntüsü: her mağaza dili ve açık platformun cihaz sınıfı için 3–5 gerçek ekran —
56
56
  `page_screenshot { applicationSlug, pageSlug, environment: 'production', device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }`
57
- → `mobile_asset_create { applicationId, kind: 'screenshot', deviceClass, locale, path }`. `applicationSlug`
58
- `application_list`'ten, `pageSlug` `page_list`'ten; `device` rehberdeki cihaz tablosundan; `language` o
57
+ → `mobile_asset_create { applicationId, kind: 'screenshot', deviceClass, locale, path }`. `applicationSlug`'a
58
+ uygulamanın id'sini ver (gateway production'ın kendi slug'ını okur; `application_list` development slug'ını döner ve
59
+ yeniden adlanan uygulamada production'da 404 verir — APP-002), `pageSlug` `page_list`'ten; `device` rehberdeki cihaz tablosundan; `language` o
59
60
  yuvanın `locale`'idir — verilmezse kare projenin varsayılan dilinde çıkar ve başka dilin yuvasına yanlış dilde
60
61
  yüklenir. Yuva birikimlidir (en çok 10 görsel): 1. adımda okuduğun yuvada görsel varsa kullanıcıya sormadan
61
62
  ekleme yapma. Giriş ve splash sayfası çekilmez. Production'da sayfa yoksa kullanıcıya sor: ekran görüntüsü
@@ -91,7 +91,7 @@ export function registerApplicationTools(server, platform) {
91
91
  }, handle(config, args => platform.org('GET', `/applications/${args.id}`)));
92
92
  server.registerTool('application_create', {
93
93
  title: 'Application oluştur',
94
- description: `Projeye yeni application ekler. ${templateSubscriptionLock}`,
94
+ description: `Projeye yeni application ekler. ORTAM (b-17, APP-001): uygulama yalnız development'ta (ve Core'da) açılır; production onu ilk deployment_start ile alır, o zamana kadar production adresi yoktur. Dallar açıldıkları andaki kopyayı tutar, sonradan açılan uygulamayı görmez. ${templateSubscriptionLock}`,
95
95
  inputSchema: {
96
96
  projectId: guid,
97
97
  slug: z.string().min(1),
@@ -113,7 +113,7 @@ export function registerApplicationTools(server, platform) {
113
113
  }));
114
114
  server.registerTool('application_update', {
115
115
  title: 'Application güncelle',
116
- description: `Application bilgilerini günceller; homepageId zorunludur (mevcut değeri application_get ile alın). ORTAM (K5, b-15): ayar alanları — properties (mobile.offline, sekmeler, geçiş…), homepageId, notFoundPageId — yalnız development'a yazılır; production onları deployment_start ile alır (dal ortamları açıldıkları andaki kopyayı tutar), yani ayarı önce development'ta deneyin. Kimlik alanları (name, slug, color, dns) bütün ortamlara hemen gider. Yanıttaki environmentNote bunu çağrı başına söyler. ${templateSubscriptionLock}`,
116
+ description: `Application bilgilerini günceller; homepageId zorunludur (mevcut değeri application_get ile alın). ORTAM (K5, b-15): bütün alanlar — name, slug, color, dns, properties (mobile.offline, sekmeler, geçiş…), homepageId, notFoundPageId — yalnız development'a (ve Core'a) yazılır; production onları deployment_start ile alır (dal ortamları açıldıkları andaki kopyayı tutar), yani önce development'ta deneyin. Slug değişirse production adresi dağıtıma kadar eski slug'la çalışır. Yanıttaki environmentNote bunu çağrı başına söyler. ${templateSubscriptionLock}`,
117
117
  inputSchema: {
118
118
  id: guid,
119
119
  slug: z.string().min(1),
@@ -136,12 +136,12 @@ export function registerApplicationTools(server, platform) {
136
136
  const missing = ['properties', 'notFoundPageId'].filter((field) => body[field] === undefined);
137
137
  return {
138
138
  ...withPlatformWarnings(response, warnings),
139
- environmentNote: `properties, homepageId ve notFoundPageId verildiği gibi yalnız development'a (ve Core'a) yazıldı; production onları deployment_start ile alır, dal ortamları hiç almaz (APP-001). name/slug/color/dns bütün ortamlara gitti.${missing.length > 0 ? ` ${missing.join(' ve ')} verilmedi: uç tam kaydı yazar, development'taki değer boşaltıldı — korumak için application_get'teki mevcut değeri gönderin.` : ''}`
139
+ environmentNote: `name, slug, color, dns, properties, homepageId ve notFoundPageId verildiği gibi yalnız development'a (ve Core'a) yazıldı; production onları deployment_start ile alır, dal ortamları hiç almaz (APP-001).${missing.length > 0 ? ` ${missing.join(' ve ')} verilmedi: uç tam kaydı yazar, development'taki değer boşaltıldı — korumak için application_get'teki mevcut değeri gönderin.` : ''}`
140
140
  };
141
141
  }));
142
142
  server.registerTool('application_delete', {
143
143
  title: 'Application sil',
144
- description: `Application'ı kalıcı olarak siler. ${templateSubscriptionLock}`,
144
+ description: `Application'ı kalıcı olarak siler — development'tan (ve Core'dan). ORTAM (b-17, APP-001): production'daki kopya ve kullanıcı verisi deployment_start'a kadar yerinde kalır, silme dağıtımla gider; dallar kendi kopyasını tutar. ${templateSubscriptionLock}`,
145
145
  inputSchema: { id: guid },
146
146
  annotations: destructive
147
147
  }, handle(config, async (args) => {
@@ -240,7 +240,8 @@ export function registerConnectDataTools(server, platform) {
240
240
  anonymous,
241
241
  mirrorAuthorizationCookie: true
242
242
  });
243
- let application = await applicationSlug(platform, applicationId);
243
+ // Development dışında slug o ortamın kendi kaydıdır; Core'un (yeni) slug'ı orada 404 verir (g-24, APP-002).
244
+ let application = await applicationSlug(platform, applicationId, { project, environment });
244
245
  if (!application)
245
246
  throw new PlatformApiError(`Uygulama ${applicationId} bulunamadı; runtime yolu kurulamadı.`, 404);
246
247
  let raw;
@@ -250,7 +251,7 @@ export function registerConnectDataTools(server, platform) {
250
251
  catch (error) {
251
252
  if (!(error instanceof PlatformApiError) || error.status !== 404)
252
253
  throw error;
253
- const fresh = await applicationSlug(platform, applicationId, { fresh: true });
254
+ const fresh = await applicationSlug(platform, applicationId, { project, environment, fresh: true });
254
255
  if (!fresh || fresh === application)
255
256
  throw error;
256
257
  application = fresh;
@@ -412,18 +413,26 @@ export function registerConnectDataTools(server, platform) {
412
413
  environment,
413
414
  body: { elementId: element, propPath: prop, name, slug, ...(version ? { baseVersion: version } : {}) }
414
415
  }));
415
- const application = await applicationSlug(platform, applicationId);
416
+ // Dönüşüm yazıldıktan sonra tarif düşmez: dağıtılmamış uygulamada yer tutucu slug ve neden döner (APP-002).
417
+ let notDeployed;
418
+ const application = await applicationSlug(platform, applicationId, { project, environment }).catch((error) => {
419
+ if (!(error instanceof PlatformApiError) || error.status !== 404)
420
+ throw error;
421
+ notDeployed = error.message;
422
+ return undefined;
423
+ });
416
424
  // Kompleks + enforce projede kimliksiz çağrı 401 AppSessionRequired döner ve yeni uç kullanıcının hiçbir
417
425
  // politikasında yoktur (39/M-15): tarif kullanıcı gözünü ve eksik grant'i söyler.
418
426
  const access = await projectAccessContext(platform, project, environment);
419
427
  const enforced = access?.accessModel === 'complex' && access.enforcementMode === 'enforce';
420
- const verifyArguments = { application: application ?? '<application slug>', microservice: result.microserviceSlug, method: 'POST', body: { op: 'list', page: { limit: 5 } }, ...(enforced ? { asUserId: "<users satır id'si>" } : {}) };
428
+ const verifyArguments = { ...(environment ? { environment } : {}), application: application ?? '<application slug>', microservice: result.microserviceSlug, method: 'POST', body: { op: 'list', page: { limit: 5 } }, ...(enforced ? { asUserId: "<users satır id'si>" } : {}) };
421
429
  return {
422
430
  ...result,
423
431
  reference: { version: 1, microserviceId: result.microserviceId },
424
432
  verify: {
425
433
  tool: 'microservice_execute',
426
434
  arguments: verifyArguments,
435
+ ...(notDeployed ? { notDeployed } : {}),
427
436
  ...(enforced ? { note: `Kompleks + enforce proje: kimliksiz çağrı 401 döner, asUserId verin. Yeni uç hiçbir politikada yok — kullanıcının politikasına microservice.execute grant'i ekleyin (resourceKey 'microservice:${result.microserviceId}'), yoksa 403. Sayfanın kendi bağlantısı bu grant'i istemez; connect_data_detach sonrası grant yetim kalır.` } : {})
428
437
  }
429
438
  };
@@ -214,25 +214,36 @@ function executePath(prefix, application, microservice, routeParams) {
214
214
  /**
215
215
  * Slug application_update ile değişebildiği için önbellek kısa ömürlüdür (yalnız aynı turdaki tekrar okumaları ucuzlatır);
216
216
  * runtime yolu 404 dönen çağıran `fresh: true` ile önbelleği atlar. Org API'nin 404 dışı hatası (yetki, ağ) çağırana gider.
217
+ *
218
+ * Uygulama kaydı (ad, slug, ayar) ve varlığı production'a yalnız dağıtımla gider, dal açıldığı andaki kopyayı tutar
219
+ * (backend ADR 0073, g-24): `GET /applications/{id}` Core'dur = development. Başka ortamın slug'ı o ortamın kendi
220
+ * kaydından (tenant `GET /{project}/applications/{id}`, X-Environment) okunur; yoksa uygulama oraya dağıtılmamıştır (APP-002).
217
221
  */
218
222
  const APPLICATION_SLUG_TTL_MS = 60_000;
219
223
  const applicationSlugCache = new Map();
220
224
  export async function applicationSlug(platform, applicationId, options = {}) {
221
- const cached = options.fresh ? undefined : applicationSlugCache.get(applicationId);
225
+ const environment = platform.resolveEnvironment(options.environment);
226
+ const core = environment === 'development';
227
+ const key = `${core ? '' : platform.resolveProject(options.project)}:${environment}:${applicationId}`;
228
+ const cached = options.fresh ? undefined : applicationSlugCache.get(key);
222
229
  if (cached && cached.expiresAt > Date.now())
223
230
  return cached.slug;
224
231
  try {
225
- const payload = await platform.org('GET', `/applications/${applicationId}`);
232
+ const payload = (core
233
+ ? await platform.org('GET', `/applications/${applicationId}`)
234
+ : await platform.tenant('GET', options.project, `/applications/${applicationId}`, { environment }));
226
235
  const application = (payload?.data ?? payload);
227
236
  const slug = typeof application.slug === 'string' && application.slug !== '' ? application.slug : undefined;
228
237
  if (slug)
229
- applicationSlugCache.set(applicationId, { slug, expiresAt: Date.now() + APPLICATION_SLUG_TTL_MS });
238
+ applicationSlugCache.set(key, { slug, expiresAt: Date.now() + APPLICATION_SLUG_TTL_MS });
230
239
  return slug;
231
240
  }
232
241
  catch (error) {
233
- if (error instanceof PlatformApiError && error.status === 404)
242
+ if (!(error instanceof PlatformApiError) || error.status !== 404)
243
+ throw error;
244
+ if (core)
234
245
  return undefined;
235
- throw error;
246
+ throw new PlatformApiError(`Uygulama ${applicationId} '${environment}' ortamında yok: uygulama kaydı (ad, slug, ayar) ve varlığı production'a yalnız dağıtımla gider, dal açıldığı andaki kopyayı tutar (APP-002). Production için deployment_start ile dağıtın; dalda yalnız dal açılmadan önce var olan uygulama bulunur — development'ta deneyin ya da dalı yeniden açın.`, 404);
236
247
  }
237
248
  }
238
249
  /**
@@ -247,7 +258,14 @@ async function resolveRuntimeUrl(platform, project, environment, applicationId,
247
258
  const method = String(definition.httpMethod ?? 'get').toUpperCase();
248
259
  const resolvedProject = platform.resolveProject(project);
249
260
  const resolvedEnvironment = platform.resolveEnvironment(environment);
250
- const application = await applicationSlug(platform, applicationId);
261
+ // Dağıtılmamış uygulamada tanım yine döner; tarif yer tutucu slug ve nedeni taşır.
262
+ let notDeployed;
263
+ const application = await applicationSlug(platform, applicationId, { project, environment }).catch((error) => {
264
+ if (!(error instanceof PlatformApiError) || error.status !== 404)
265
+ throw error;
266
+ notDeployed = error.message;
267
+ return undefined;
268
+ });
251
269
  const path = `/${resolvedProject}/m/${application ?? '<application slug>'}/${slug}`;
252
270
  return {
253
271
  method,
@@ -257,7 +275,7 @@ async function resolveRuntimeUrl(platform, project, environment, applicationId,
257
275
  Host: `${platform.config.organization}.${platform.config.hostSuffix}`,
258
276
  'X-Environment': resolvedEnvironment
259
277
  },
260
- note: `Yolu SLUG kurar, 'url' alanı ('${String(definition.url ?? '')}') rotaya girmez. Route parametreleri slug'ın ardına eklenir. Anonim erişim testi için microservice_execute'u anonymous: true ile çağırın; korumalı uçlarda 401, anonim uçlarda 200 beklenir.`
278
+ note: `Yolu SLUG kurar, 'url' alanı ('${String(definition.url ?? '')}') rotaya girmez. Route parametreleri slug'ın ardına eklenir. Anonim erişim testi için microservice_execute'u anonymous: true ile çağırın; korumalı uçlarda 401, anonim uçlarda 200 beklenir.${notDeployed ? ` ${notDeployed}` : ''}`
261
279
  };
262
280
  }
263
281
  export function registerMicroserviceTools(server, platform) {
@@ -590,7 +590,7 @@ export function registerMobileStoreTools(server, platform, storeArt = {}) {
590
590
  }, handle(config, async ({ buildId: id }) => buildSummary(unwrap(await platform.org('POST', `/mobile-builds/${id}/cancel`)), new Date().toISOString())));
591
591
  server.registerTool('mobile_asset_create', {
592
592
  title: 'Mağaza görseli yükle',
593
- description: `Mağaza görselini yükler (POST mobile-store/{appId}/assets, multipart). Kaynak path (gateway'in okuyabildiği yerel dosya) ya da base64 content'tir — dosya ajanın bağlamına girmez. Yuvalar: derleme görselleri icon (1024×1024 PNG, alfasız), adaptiveForeground / adaptiveBackground / adaptiveMonochrome (1024×1024 PNG), splash (≥ 1242×2436 PNG) — dilsizdir, locale verilmez; mağaza görselleri featureGraphic (1024×500, alfasız), promo, screenshot (deviceClass zorunlu, locale önerilir: 'screenshot/tr/iphone69'); reviewAttachment (dilsiz, PNG/JPEG) App Review'a ek görseldir — yalnız app-store-review gönderimine girer. Tuzak: screenshot ve promo dışındaki türler TEKLİDİR — yükleme yuvadaki önceki görselin YERİNE geçer ve eskisi silinir. Yuva başına en çok 10 ekran görüntüsü, dosya ≤ 10 MB, PNG ya da JPEG (derleme görseli yalnız PNG). Ölçü, biçim ve alfa backend'de denetlenir; 422 hangi ölçünün beklendiğini söyler. EKRAN GÖRÜNTÜSÜ uygulamanın kendi sayfasından üretilir: önce page_screenshot { environment: 'production', applicationSlug, pageSlug, device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }, sonra bu araç { path: <outPath>, kind: 'screenshot', deviceClass, locale }. Cihaz sınıfı → device: ${captureTable}. JPEG şarttır: iPhone ve iPad ekran görüntüsünde alfa kanalı reddedilir. Giriş/splash ekranını çekmeyin (App Review 2.3.3: uygulama kullanımda gösterilir). Sıra: mobile_asset_update; silme: mobile_asset_delete; liste: mobile_store_get { detail: ['assets'] }.`,
593
+ description: `Mağaza görselini yükler (POST mobile-store/{appId}/assets, multipart). Kaynak path (gateway'in okuyabildiği yerel dosya) ya da base64 content'tir — dosya ajanın bağlamına girmez. Yuvalar: derleme görselleri icon (1024×1024 PNG, alfasız), adaptiveForeground / adaptiveBackground / adaptiveMonochrome (1024×1024 PNG), splash (≥ 1242×2436 PNG) — dilsizdir, locale verilmez; mağaza görselleri featureGraphic (1024×500, alfasız), promo, screenshot (deviceClass zorunlu, locale önerilir: 'screenshot/tr/iphone69'); reviewAttachment (dilsiz, PNG/JPEG) App Review'a ek görseldir — yalnız app-store-review gönderimine girer. Tuzak: screenshot ve promo dışındaki türler TEKLİDİR — yükleme yuvadaki önceki görselin YERİNE geçer ve eskisi silinir. Yuva başına en çok 10 ekran görüntüsü, dosya ≤ 10 MB, PNG ya da JPEG (derleme görseli yalnız PNG). Ölçü, biçim ve alfa backend'de denetlenir; 422 hangi ölçünün beklendiğini söyler. EKRAN GÖRÜNTÜSÜ uygulamanın kendi sayfasından üretilir: önce page_screenshot { environment: 'production', applicationSlug: <uygulama id'si — production'ın kendi slug'ına çözülür, APP-002>, pageSlug, device, format: 'jpeg', quality: 92, language, colorScheme: 'light', outPath }, sonra bu araç { path: <outPath>, kind: 'screenshot', deviceClass, locale }. Cihaz sınıfı → device: ${captureTable}. JPEG şarttır: iPhone ve iPad ekran görüntüsünde alfa kanalı reddedilir. Giriş/splash ekranını çekmeyin (App Review 2.3.3: uygulama kullanımda gösterilir). Sıra: mobile_asset_update; silme: mobile_asset_delete; liste: mobile_store_get { detail: ['assets'] }.`,
594
594
  inputSchema: {
595
595
  applicationId,
596
596
  path: z.string().min(1).optional().describe("Gateway sürecinin okuyabildiği yerel PNG/JPEG yolu (page_screenshot outPath'i); content ile birlikte verilmez"),
@@ -30,6 +30,8 @@ import { boxSectionNote, collectIds, ensureBoxSections, expandSectionToggles } f
30
30
  import { designSnapshotInput } from '../knowledge/designSnapshot.js';
31
31
  import { discoverPage } from '../knowledge/pageDiscover.js';
32
32
  import { applicationTypeField, resolveProfile, resolveProfileBySlug } from '../knowledge/platform/resolve.js';
33
+ import { applicationSlug as environmentApplicationSlug } from './microservices.js';
34
+ import { isGuid } from '../platform/refs.js';
33
35
  import { batchEnvelopeNote, batchIndexHint, openBatchEnvelopes } from '../knowledge/platform/batchOps.js';
34
36
  import { heroTags } from '../knowledge/platform/heroTag.js';
35
37
  import { profileLintReadsSavedNodes } from '../knowledge/platform/lint.js';
@@ -58,6 +60,20 @@ function runtimeFunctions(value) {
58
60
  .map(parameter => ({ name: parameter.name, required: parameter.required === true, defaultValue: parameter.defaultValue ?? null }))
59
61
  }));
60
62
  }
63
+ /**
64
+ * g-24 / APP-002: uygulama kaydı production'a yalnız dağıtımla gider, dal açıldığı andaki kopyayı tutar; application_list
65
+ * ve application_get Core'u (development) döner. Yeniden adlanan uygulamanın production/dal adresi eski slug'la çalışır,
66
+ * Core slug'ı orada 404 verir — id verilirse slug'ı o ortamın kaydından gateway okur.
67
+ */
68
+ const runtimeApplicationParam = z.string().min(1).describe("Uygulamanın slug'ı ya da id'si (GUID). application_list / application_get DEVELOPMENT slug'ını döner; production ve dal uygulama kaydını yalnız dağıtımla/açılışla alır, yeniden adlanan uygulama orada dağıtıma kadar eski slug'la açılır. production ya da dalda id verin: gateway o ortamın kendi slug'ını okur, uygulama o ortamda yoksa (dağıtılmamış) istek gitmeden hata döner (APP-002)");
69
+ async function runtimeApplicationSlug(platform, project, ref, environment) {
70
+ if (!isGuid(ref))
71
+ return ref;
72
+ const slug = await environmentApplicationSlug(platform, ref, { project, environment });
73
+ if (!slug)
74
+ throw new PlatformApiError(`Uygulama ${ref} bulunamadı: id'yi application_list ile doğrulayın.`, 404);
75
+ return slug;
76
+ }
61
77
  /**
62
78
  * G-10: `index` taşıyan add/move op'u varsa sayfa okunur ve `flexIndexOrder` flex kapta görünen
63
79
  * sırayı konumlara çevirir. Okuma başarısız olursa op'lar olduğu gibi gider — bugünkü davranış.
@@ -181,7 +197,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
181
197
  inputSchema: {
182
198
  project: projectParam,
183
199
  environment: environmentParam,
184
- applicationSlug: z.string().min(1).describe("Uygulamanın slug'ı (application_list / project_overview)"),
200
+ applicationSlug: runtimeApplicationParam,
185
201
  pageSlug: z.string().min(1).describe("Sayfanın slug'ı (page_list)"),
186
202
  view: z.enum(['summary', 'full']).optional().describe('summary (varsayılan): şema/layout ağaçları id/name/type özetine indirgenir. full: ham gövde — büyük olabilir'),
187
203
  resolveStyles: z.boolean().optional().describe("summary görünümünde her düğüme EFEKTİF stilini ekler: tema componentStyles → preset → düğümün kendi stili kaskadı çözülür (aibox'ta tipli stil grupları da derlenir ve TABAN katman olur). styleLayers hangi katmanın konuştuğunu söyler — 'bu kutu neden böyle görünüyor' sorusunun cevabı budur."),
@@ -190,7 +206,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
190
206
  asUserId: asUserIdParam
191
207
  },
192
208
  annotations: readOnly
193
- }, handle(config, async ({ project, environment, applicationSlug, pageSlug, view, resolveStyles, anonymous, asUser, asUserId }) => {
209
+ }, handle(config, async ({ project, environment, applicationSlug: applicationRef, pageSlug, view, resolveStyles, anonymous, asUser, asUserId }) => {
194
210
  let response;
195
211
  if (anonymous === true && asUser !== undefined) {
196
212
  throw new PlatformApiError('page_runtime_get: anonymous:true ile asUser birlikte verilemez — biri kimliksiz isteği, diğeri belirli bir üyenin kimliğiyle atılan isteği ister.');
@@ -199,6 +215,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
199
215
  // yapısal olarak geçemiyordu. Kimlik artık iki kanalda birden gider (Y-02).
200
216
  const eye = await impersonationHeaders(platform, 'page_runtime_get', project, environment, { asUserId, asUser, anonymous }, { runtime: true });
201
217
  const identity = asUser === undefined ? (eye ? { headers: eye } : {}) : { headers: { authorization: `Bearer ${await resolveIdentity(asUser, platform, project, environment)}` } };
218
+ const applicationSlug = await runtimeApplicationSlug(platform, project, applicationRef, environment);
202
219
  try {
203
220
  response = await platform.tenant('GET', project, `/${applicationSlug}/p/${pageSlug}`, { environment, anonymous, mirrorAuthorizationCookie: true, ...identity });
204
221
  }
@@ -210,7 +227,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
210
227
  status: error.status,
211
228
  message: error.message,
212
229
  ...(decision ? { accessDecision: decision } : {}),
213
- note: "Sayfaya bağlı middleware isteği reddetti ya da kayıt bulunamadı. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır. Sayfanın bağlarını page_get ile, middleware tarifini platform_guide('middleware') ile kontrol edin; slug'ları page_list/application_list doğrular."
230
+ note: "Sayfaya bağlı middleware isteği reddetti ya da kayıt bulunamadı. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır. Sayfanın bağlarını page_get ile, middleware tarifini platform_guide('middleware') ile kontrol edin; slug'ları page_list/application_list doğrular — application_list development slug'ını verir; production ya da dalda applicationSlug yerine uygulama id'sini verin (APP-002)."
214
231
  };
215
232
  }
216
233
  throw error;
@@ -272,11 +289,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
272
289
  const maxInlinePngBytes = 5 * 1024 * 1024;
273
290
  server.registerTool('page_screenshot', {
274
291
  title: 'Sayfanın ekran görüntüsünü al',
275
- description: "Telefonda (mağaza kabuğu ya da oynatıcı) düşen akış hatasını bu araç görmez — onu mobile_error_list okur. ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Bir komponent prop'unun İFADESİ patlarsa (Studio'da 'Expression error' toast'ı) yanıt expressionErrors taşır: {elementId, prop, expression, message, count}, öğe başına en çok iki; aynı satırın konsol kopyası consoleErrors'a düşmez. Sayfa ya da bir bileşen ÇİZİLEMEZSE neden yanıttadır: mobilde çöken düğüm renderErrors ({elementId, message}; sayfanın geri kalanı çizilir), yakalanmamış istisna pageErrors, konsol hatası consoleErrors — kare alınamadıysa bütün nedenler hata metninde. KARE DONUKTUR: tarayıcı bağlamı 'hareketi azalt' tercihiyle açılır; mobil runtime bileşen hareketini (giriş, döngü, geçiş, iskelet parıltısı, yükleme göstergesi) durdurur ve aynı sayfanın iki karesi tekrarlanabilir. Web sayfasında yalnız CSS'i prefers-reduced-motion'a uyan animasyonlar durur. Hareketin kendisini görmek için motion:'play'. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama).",
292
+ description: "Telefonda (mağaza kabuğu ya da oynatıcı) düşen akış hatasını bu araç görmez — onu mobile_error_list okur. ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Bir komponent prop'unun İFADESİ patlarsa (Studio'da 'Expression error' toast'ı) yanıt expressionErrors taşır: {elementId, prop, expression, message, count}, öğe başına en çok iki; aynı satırın konsol kopyası consoleErrors'a düşmez. Sayfa ya da bir bileşen ÇİZİLEMEZSE neden yanıttadır: mobilde çöken düğüm renderErrors ({elementId, message}; sayfanın geri kalanı çizilir), yakalanmamış istisna pageErrors, konsol hatası consoleErrors — kare alınamadıysa bütün nedenler hata metninde. KARE DONUKTUR: tarayıcı bağlamı 'hareketi azalt' tercihiyle açılır; mobil runtime bileşen hareketini (giriş, döngü, geçiş, iskelet parıltısı, yükleme göstergesi) durdurur ve aynı sayfanın iki karesi tekrarlanabilir. Web sayfasında yalnız CSS'i prefers-reduced-motion'a uyan animasyonlar durur. Hareketin kendisini görmek için motion:'play'. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama). Production ve dalda uygulamanın slug'ı o ortamın kendi kaydıdır (dağıtıma/açılışa kadar eski slug; application_list development'ı döner): applicationSlug'a uygulama id'sini verin, gateway ortamın slug'ını okur (APP-002).",
276
293
  inputSchema: {
277
294
  project: projectParam,
278
295
  environment: environmentParam,
279
- applicationSlug: z.string().min(1).describe("Uygulamanın slug'ı (application_list / project_overview)"),
296
+ applicationSlug: runtimeApplicationParam,
280
297
  pageSlug: z.string().min(1).describe("Sayfanın slug'ı (page_list); sayfa parametresi sorguyla verilebilir: 'ilac-detay?id=7' (language '&lang=' olarak eklenir)"),
281
298
  viewport: z.object({
282
299
  width: z.number().int().min(320).max(3840),
@@ -326,7 +343,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
326
343
  outPath: z.string().min(1).optional().describe('Verilirse PNG bu yerel yola yazılır ve inline görüntü DÖNMEZ; yalnız özet (yol, boyut) döner — büyük fullPage kareleri için')
327
344
  },
328
345
  annotations: readOnly
329
- }, async ({ project, environment, applicationSlug, pageSlug, viewport, device, fullPage, delayMs, captureUnready, anonymous, asUser, asUserId, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, motion, summary, image, format, quality, scale, clip, diff, session }) => {
346
+ }, async ({ project, environment, applicationSlug: applicationRef, pageSlug, viewport, device, fullPage, delayMs, captureUnready, anonymous, asUser, asUserId, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, motion, summary, image, format, quality, scale, clip, diff, session }) => {
330
347
  try {
331
348
  if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
332
349
  throw new PlatformApiError("page_screenshot: anonymous:true ile asUser/cookies birlikte verilemez — biri kimliksiz kareyi, diğeri belirli bir kimlikle çekilen kareyi ister. Anonim kare için yalnız anonymous:true, üye kimliğiyle kare için yalnız asUser (ve gerekiyorsa cookies) gönderin.");
@@ -398,6 +415,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
398
415
  // 37 S-17 / 38 S-13: sorgulu slug (`ilac-detay?id=7`) dizeyle birleşince dil ikinci bir '?' ile
399
416
  // ekleniyordu ve runtime `id`'yi '7?lang=tr' okudu; sayfa isteği işareti de sorguyu taşıdığı
400
417
  // için sayfanın kendi veri isteğiyle hiç eşleşmiyordu.
418
+ const applicationSlug = await runtimeApplicationSlug(platform, project, applicationRef, environment);
401
419
  const address = new URL(`${config.studioBaseUrl}/${lane}/${projectSlug}/${applicationSlug}/${pageSlug}`);
402
420
  if (languageChoice)
403
421
  address.searchParams.set('lang', languageChoice.code);
@@ -405,7 +423,8 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
405
423
  const pagePath = pageSlug.split(/[?#]/, 1)[0];
406
424
  // Hedef ve cihaz uygulamanın platform profilinden (G3): mobil uygulama react-native-web
407
425
  // çizimidir, varsayılan kare telefon ön ayarıdır. Tip okunamazsa web davranışı aynen.
408
- const resolved = await resolveProfileBySlug(platform, projectSlug, applicationSlug);
426
+ // Eski production slug'ı Core'da yoktur: id verildiyse tip id'den okunur, slug'la arama web'e düşerdi (APP-002).
427
+ const resolved = isGuid(applicationRef) ? await resolveProfile(platform, applicationRef) : await resolveProfileBySlug(platform, projectSlug, applicationSlug);
409
428
  const target = captureTargetFor(resolved.profile.capture.target);
410
429
  const mobile = resolved.profile.platform === 'mobile';
411
430
  if (device !== undefined && !mobile) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.53",
3
+ "version": "0.0.54",
4
4
  "type": "module",
5
5
  "description": "Zeyrek (NgApps) platform APIs as MCP tools — data, logic and Studio design surface in one gateway",
6
6
  "license": "UNLICENSED",