@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6

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 (101) hide show
  1. package/.env.example +8 -3
  2. package/.env.production.example +2 -0
  3. package/README.md +229 -193
  4. package/dist/config.js +20 -2
  5. package/dist/knowledge/backendConstraints.js +897 -0
  6. package/dist/knowledge/componentCatalog.js +196 -42
  7. package/dist/knowledge/coverage.js +43 -0
  8. package/dist/knowledge/flowGuards.js +30 -0
  9. package/dist/knowledge/guideSections.js +49 -0
  10. package/dist/knowledge/hydrate.js +97 -0
  11. package/dist/knowledge/languageCodes.js +36 -0
  12. package/dist/knowledge/languages.js +105 -0
  13. package/dist/knowledge/layoutSummary.js +10 -0
  14. package/dist/knowledge/localization.js +51 -0
  15. package/dist/knowledge/normalize.js +449 -16
  16. package/dist/knowledge/outlines.js +136 -12
  17. package/dist/knowledge/propTypes.js +80 -0
  18. package/dist/knowledge/rules.js +618 -43
  19. package/dist/knowledge/schemaGuard.js +199 -0
  20. package/dist/knowledge/styleResolve.js +63 -4
  21. package/dist/observability/trace.js +27 -3
  22. package/dist/platform/errors.js +46 -4
  23. package/dist/platform/http.js +33 -4
  24. package/dist/platform/patch.js +27 -0
  25. package/dist/platform/refs.js +104 -0
  26. package/dist/platform/responses.js +9 -2
  27. package/dist/platform/screenshot.js +527 -16
  28. package/dist/platform/secrets.js +40 -0
  29. package/dist/platform/tokenVault.js +89 -0
  30. package/dist/platform/writeDiff.js +281 -0
  31. package/dist/resources/examples/layout-app-shell.json +1 -1
  32. package/dist/resources/examples/microservice-table-list.json +2 -4
  33. package/dist/resources/examples/page-dynamic-form.json +2 -2
  34. package/dist/resources/examples/page-flex-sections.json +99 -0
  35. package/dist/resources/examples/page-sign-in.json +3 -2
  36. package/dist/resources/guides/component.md +69 -8
  37. package/dist/resources/guides/entity.md +404 -16
  38. package/dist/resources/guides/expressions.md +44 -2
  39. package/dist/resources/guides/external-component.md +9 -0
  40. package/dist/resources/guides/getting-started.md +77 -7
  41. package/dist/resources/guides/layout.md +53 -10
  42. package/dist/resources/guides/microservice.md +226 -13
  43. package/dist/resources/guides/middleware.md +89 -1
  44. package/dist/resources/guides/page.md +383 -46
  45. package/dist/resources/guides/translation.md +126 -8
  46. package/dist/resources/guides/view.md +16 -1
  47. package/dist/resources/guides/workflow.md +198 -27
  48. package/dist/resources/knowledge.js +5 -0
  49. package/dist/resources/rules.js +27 -0
  50. package/dist/resources/schema-fingerprint.json +8 -0
  51. package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
  52. package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
  53. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
  54. package/dist/resources/schemas/CreateEntityCommand.json +52 -12
  55. package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
  56. package/dist/resources/schemas/CreateQueryCommand.json +56 -3
  57. package/dist/resources/schemas/CreateRowCommand.json +38 -0
  58. package/dist/resources/schemas/CreateViewCommand.json +13 -10
  59. package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
  60. package/dist/resources/schemas/GetRowsQuery.json +3 -0
  61. package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
  62. package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
  64. package/dist/resources/schemas/StepDto.json +23 -3
  65. package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
  66. package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
  67. package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
  68. package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
  69. package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
  70. package/dist/resources/schemas/UpdateRowCommand.json +40 -0
  71. package/dist/resources/schemas/UpdateViewCommand.json +15 -10
  72. package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
  73. package/dist/server.js +4 -0
  74. package/dist/tools/access.js +3 -3
  75. package/dist/tools/applications.js +1 -1
  76. package/dist/tools/automations.js +63 -30
  77. package/dist/tools/common.js +39 -0
  78. package/dist/tools/components.js +16 -7
  79. package/dist/tools/datasources.js +83 -21
  80. package/dist/tools/deployments.js +34 -2
  81. package/dist/tools/dtos.js +25 -12
  82. package/dist/tools/entities.js +113 -28
  83. package/dist/tools/externalComponents.js +9 -3
  84. package/dist/tools/files.js +20 -6
  85. package/dist/tools/flows.js +110 -11
  86. package/dist/tools/folders.js +7 -2
  87. package/dist/tools/globalFunctions.js +63 -0
  88. package/dist/tools/layouts.js +103 -29
  89. package/dist/tools/microservices.js +229 -38
  90. package/dist/tools/organizations.js +5 -6
  91. package/dist/tools/pages.js +365 -100
  92. package/dist/tools/platformInfo.js +152 -11
  93. package/dist/tools/projects.js +183 -33
  94. package/dist/tools/queries.js +32 -10
  95. package/dist/tools/rows.js +159 -45
  96. package/dist/tools/screenWrite.js +59 -0
  97. package/dist/tools/screens.js +28 -0
  98. package/dist/tools/translations.js +24 -8
  99. package/dist/tools/views.js +20 -10
  100. package/dist/tools/workflows.js +33 -16
  101. package/package.json +6 -3
@@ -0,0 +1,49 @@
1
+ const turkishMap = { ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u', â: 'a', î: 'i', û: 'u' };
2
+ export function sectionSlug(title) {
3
+ return title.toLocaleLowerCase('tr')
4
+ .replace(/[çğıöşüâîû]/g, letter => turkishMap[letter] ?? letter)
5
+ .replace(/[^a-z0-9]+/g, '-')
6
+ .replace(/^-+|-+$/g, '');
7
+ }
8
+ /**
9
+ * Rehberler `## ` başlıklarıyla bölümlü; `page.md` 120 KB'ı aşınca tool çıktı sınırına takıldı ve
10
+ * MCP kanalından hiç okunamadı (09-04 koşusu). Bölümleme var olan başlıkları kullanır, dosyaların
11
+ * içeriğine dokunmaz.
12
+ *
13
+ * Satır sonu ÖNEMLİ: başlık ifadesindeki `.` satır sonu karakterlerini eşlemez ve `$` çok satırlı
14
+ * değildir, yani `\r` ile biten bir satırda hiçbir başlık görünmez. Windows çalışma ağacında
15
+ * (`.gitattributes` yokken) rehberlerin tamamı CRLF olduğu için bölümleyici tek "giriş" bölümü
16
+ * üretiyor, `platform_guide` de oversize dalında rehberin TAMAMINI döndürüp tool sınırını
17
+ * aşıyordu — ölçüldü 2026-09-05, `page.md` 22 başlık → 1 bölüm. Girdi bu yüzden bölünmeden önce
18
+ * satır sonlarından arındırılır.
19
+ */
20
+ export function splitGuideSections(markdown) {
21
+ const sections = [];
22
+ const used = new Map();
23
+ let fence = '';
24
+ let title = '';
25
+ let lines = [];
26
+ const push = () => {
27
+ if (title === '' && lines.join('\n').trim() === '')
28
+ return;
29
+ const base = title === '' ? 'giris' : sectionSlug(title);
30
+ const seen = (used.get(base) ?? 0) + 1;
31
+ used.set(base, seen);
32
+ sections.push({ slug: seen === 1 ? base : `${base}-${seen}`, title: title === '' ? 'Giriş' : title, body: lines.join('\n').trim() });
33
+ };
34
+ for (const line of markdown.split(/\r\n|\r|\n/)) {
35
+ const marker = /^\s*(`{3,})/.exec(line)?.[1] ?? '';
36
+ if (marker.length > 0)
37
+ fence = fence === '' ? marker : (marker.length >= fence.length ? '' : fence);
38
+ const heading = fence === '' ? /^## (?!#)(.+)$/.exec(line)?.[1] : undefined;
39
+ if (heading === undefined) {
40
+ lines.push(line);
41
+ continue;
42
+ }
43
+ push();
44
+ title = heading.trim();
45
+ lines = [];
46
+ }
47
+ push();
48
+ return sections;
49
+ }
@@ -0,0 +1,97 @@
1
+ import { RefResolveError, isGuid } from '../platform/refs.js';
2
+ const fieldCarriers = ['fields', 'selectFields', 'returnFields'];
3
+ function entries(value) {
4
+ return Array.isArray(value) ? value.filter((entry) => Boolean(entry) && typeof entry === 'object') : [];
5
+ }
6
+ function collectJoinAliases(joins, into) {
7
+ for (const join of entries(joins)) {
8
+ const alias = String(join.alias ?? '').trim();
9
+ if (alias)
10
+ into.add(alias);
11
+ collectJoinAliases(join.joins, into);
12
+ }
13
+ return into;
14
+ }
15
+ function pendingEntries(parameters) {
16
+ const pending = [];
17
+ const walk = (list) => {
18
+ for (const entry of entries(list)) {
19
+ if (typeof entry.fieldSlug === 'string' && typeof entry.fieldId !== 'string')
20
+ pending.push(entry);
21
+ walk(entry.children);
22
+ }
23
+ };
24
+ for (const key of fieldCarriers)
25
+ walk(parameters[key]);
26
+ walk(parameters.filters);
27
+ return pending;
28
+ }
29
+ /**
30
+ * Hydration normalize'dan ÖNCE koşmak zorundadır: `normalizeTableParameters` join ve seçim
31
+ * yollarını `fieldId`'ye göre üretir (`normalize.ts` `fillJoinPaths`), boş `fieldId` gören
32
+ * normalize yolu hiç doldurmaz.
33
+ *
34
+ * GUID'li ve `fieldSlug` taşımayan tanımlar hiç ağ turu ödemez ve ağaç hiç okunmaz — ağaç
35
+ * uçtaki gizli entity'leri (Files/Folders, EnumValues) TAŞIMAZ, dolayısıyla koşulsuz çözüm
36
+ * bugüne kadar çalışan GUID'li çağrıları "ağaçta yok" diye düşürürdü.
37
+ *
38
+ * Kapsam bilinçli olarak KÖK entity ile sınırlı: `joins[]` girdileri şemada `fieldSlug`
39
+ * taşımaz (`TableJoin` = alias·entityId·fieldId·joins·path), join'lenen entity'nin alanları
40
+ * ayrı bir görev.
41
+ */
42
+ export async function hydrateMicroserviceRefs(definition, resolver) {
43
+ if (definition.source !== 'table')
44
+ return [];
45
+ const rootRef = String(definition.entityId ?? '');
46
+ if (rootRef === '')
47
+ return [];
48
+ const parameters = (definition.sourceParameters ?? {});
49
+ const nestedRef = String(parameters.entityId ?? '');
50
+ const pending = pendingEntries(parameters);
51
+ if (pending.length === 0 && isGuid(rootRef) && (nestedRef === '' || isGuid(nestedRef)))
52
+ return [];
53
+ const fixes = [];
54
+ const root = await resolver.entity(rootRef);
55
+ if (!isGuid(rootRef)) {
56
+ definition.entityId = root.entityId;
57
+ fixes.push(`entityId '${rootRef}' → ${root.entityId}`);
58
+ }
59
+ if (nestedRef !== '' && !isGuid(nestedRef)) {
60
+ parameters.entityId = (await resolver.entity(nestedRef)).entityId;
61
+ fixes.push(`sourceParameters.entityId '${nestedRef}' → ${String(parameters.entityId)}`);
62
+ }
63
+ if (pending.length === 0)
64
+ return fixes;
65
+ const fields = await resolver.entityFields(root);
66
+ const idBySlug = new Map(fields.map(field => [field.slug, field.id]));
67
+ const joinAliases = collectJoinAliases(parameters.joins, new Set());
68
+ const declaredAlias = String(parameters.alias ?? '').trim();
69
+ const rootAlias = declaredAlias === '' ? root.slug : declaredAlias;
70
+ const fill = (entry, where) => {
71
+ const slug = typeof entry.fieldSlug === 'string' ? entry.fieldSlug : undefined;
72
+ const alias = String(entry.alias ?? '').trim();
73
+ if (slug === undefined || typeof entry.fieldId === 'string')
74
+ return;
75
+ // Kök girdi: alias yok, kök alias'ı, ya da hiçbir join'e ait değil — `normalize.fillRootAlias`
76
+ // kök alias'ı tam da bu çıkarımla buluyor; ikisi ayrışırsa filtre sessizce fieldId'siz gider.
77
+ if (alias !== '' && alias !== rootAlias && joinAliases.has(alias))
78
+ return;
79
+ const id = idBySlug.get(slug);
80
+ if (id === undefined) {
81
+ throw new RefResolveError(`'${slug}' diye bir alan '${root.slug}' entity'sinde yok (${where}). Mevcut alanlar: ${[...idBySlug.keys()].join(', ')}`);
82
+ }
83
+ entry.fieldId = id;
84
+ fixes.push(`${where}.fieldSlug '${slug}' → ${id}`);
85
+ };
86
+ for (const key of fieldCarriers) {
87
+ entries(parameters[key]).forEach((entry, index) => fill(entry, `${key}[${index}]`));
88
+ }
89
+ const walkFilters = (list, prefix) => {
90
+ entries(list).forEach((entry, index) => {
91
+ fill(entry, `${prefix}[${index}]`);
92
+ walkFilters(entry.children, `${prefix}[${index}].children`);
93
+ });
94
+ };
95
+ walkFilters(parameters.filters, 'filters');
96
+ return fixes;
97
+ }
@@ -0,0 +1,36 @@
1
+ import { PlatformApiError } from '../platform/errors.js';
2
+ const ISO_639_1 = /^[a-z]{2}$/;
3
+ /**
4
+ * Platformun tek dil kodu biçimi ISO 639-1 küçük harftir (sözleşme D2). Dışarıdan gelen BCP-47
5
+ * değer ('tr-TR', 'en_US') birincil alt etikete indirgenir ve bölge saklanmaz — backend de aynısını
6
+ * yapar (`LanguageContext.RequestedLanguages`). İndirgeme SESSİZ yapılmaz: 'pt-BR' gönderip 'pt'
7
+ * kaydedildiğini bilmeyen çağıran, bölge ayrımının platformda hiç olmadığını fark etmiyor.
8
+ */
9
+ export function normalizeLanguageCode(input, field) {
10
+ const code = (input.trim().split(/[-_]/)[0] ?? '').toLowerCase();
11
+ if (!ISO_639_1.test(code)) {
12
+ throw new PlatformApiError(`'${field}' alanındaki '${input}' geçerli bir dil kodu değil. Platform ISO 639-1 küçük harf iki harfli kod tutar ('tr', 'en', 'de'); bölgeli değer ('tr-TR') kabul edilir ve indirgenir ama '${input}' indirgendiğinde de iki harfe inmiyor.`, 400);
13
+ }
14
+ return code === input ? { code } : { code, note: `${field}: '${input}' → '${code}' (platform ISO 639-1 küçük harf kod tutar, bölge bilgisi saklanmaz).` };
15
+ }
16
+ export function normalizeLanguageCodes(inputs, field) {
17
+ const results = inputs.map(input => normalizeLanguageCode(input, field));
18
+ return {
19
+ codes: results.map(result => result.code),
20
+ notes: results.flatMap(result => result.note ? [result.note] : [])
21
+ };
22
+ }
23
+ export const DATA_LANGUAGE_HEADER = 'X-Data-Language';
24
+ /**
25
+ * Yürütme araçlarının `language` parametresini backend'in istek dili başlığına çevirir. Başlık
26
+ * gönderilmezse backend `Accept-Language`'a, o da yoksa projenin varsayılan diline düşer
27
+ * (sözleşme D3) — yani parametreyi atlamak bir hata değil, "projenin varsayılanı" demektir.
28
+ */
29
+ export function languageHeader(input, notes) {
30
+ if (input === undefined)
31
+ return {};
32
+ const { code, note } = normalizeLanguageCode(input, 'language');
33
+ if (note)
34
+ notes.push(note);
35
+ return { [DATA_LANGUAGE_HEADER]: code };
36
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Projenin dil durumunu `project_get` / `/p/{slug}` yanıtından okur.
3
+ *
4
+ * `ProjectDto.Languages` null alanları serileştirmede DÜŞÜYOR: dili kurulmamış projede yanıtta
5
+ * `languages` diye bir anahtar hiç yok — "boş liste" bile görünmüyor (canlı doğrulama 2026-09-09,
6
+ * `mcp-benchmark-2026-09-09`). Ajan bu yüzden eksikliği fark edemiyordu; yokluk ile boş dizi
7
+ * runtime açısından aynı şeydir ve burada aynı şekilde temsil edilir.
8
+ *
9
+ * Alan var ama beklenmedik şekildeyse (dizi değilse) `undefined` döner: gateway o durumda
10
+ * SUSAR, "dil yok" diye uydurmaz.
11
+ */
12
+ export function readProjectLanguages(payload) {
13
+ const data = payload?.data;
14
+ if (!data || typeof data !== 'object')
15
+ return undefined;
16
+ const record = data;
17
+ const raw = record.languages;
18
+ if (raw !== undefined && raw !== null && !Array.isArray(raw))
19
+ return undefined;
20
+ const languages = Array.isArray(raw) ? raw.filter((entry) => typeof entry === 'string') : [];
21
+ const defaultLanguage = typeof record.defaultLanguage === 'string' && record.defaultLanguage !== ''
22
+ ? record.defaultLanguage
23
+ : undefined;
24
+ return { languages, defaultLanguage };
25
+ }
26
+ /**
27
+ * Dil verilmeden açılan projeye gateway'in tohumladığı ayar.
28
+ *
29
+ * Backend'in create ucu (`CreateOrganizationProjectCommand`) `Languages`/`DefaultLanguage` alanı
30
+ * TAŞIMAZ: proje ikisi de `null` doğar ve o hâlde yazılan her çeviri runtime'da ham anahtar olarak
31
+ * çizilir (`S-15`). Tohum bu durumu doğuştan imkânsız kılar.
32
+ *
33
+ * TEK DİL: ikinci bir dil tohumlamak `language-selector`'ı her yeni uygulamada iki seçenekle
34
+ * çizerdi ve ikincisi hep boş olurdu. Çok dilli uygulama kuran çağıran `languages`'ı açıkça verir.
35
+ *
36
+ * Backend de aynı tohumu kendi kurucusunda yazıyor (`ProjectConstants.DefaultLanguages`), ama o
37
+ * değişiklik dağıtılana kadar çalışan platform hâlâ dilsiz proje doğuruyor — gateway bu yüzden
38
+ * tohumu koşulsuz yazar. İki taraf aynı değeri tuttuğu için fazladan PUT zararsızdır; backend
39
+ * sürümü kesinleştiğinde bu adım yalnız `languages` açıkça verildiğinde koşacak şekilde daraltılır.
40
+ */
41
+ export const SEED_LANGUAGES = ['en'];
42
+ export const SEED_DEFAULT_LANGUAGE = 'en';
43
+ /**
44
+ * Çağıranın verdiğini olduğu gibi kullanır, eksiği tamamlar, hiçbiri yoksa tohumu döndürür.
45
+ * `defaultLanguage` her zaman `languages` içindedir — PRJ-002 bunu 422 ile zorunlu tutuyor.
46
+ */
47
+ export function resolveSeedLanguages(languages, defaultLanguage) {
48
+ if (!languages?.length && defaultLanguage === undefined) {
49
+ return { languages: SEED_LANGUAGES, defaultLanguage: SEED_DEFAULT_LANGUAGE, seeded: true };
50
+ }
51
+ const resolved = languages?.length ? languages : [defaultLanguage];
52
+ return { languages: resolved, defaultLanguage: defaultLanguage ?? resolved[0], seeded: false };
53
+ }
54
+ /** Runtime'ın çeviri çözümü kayıtlı bir aktif dil ya da varsayılan dil olmadan anahtara düşer. */
55
+ export function runtimeResolvesTranslations(state) {
56
+ return state.languages.length > 0 && state.defaultLanguage !== undefined;
57
+ }
58
+ const CHAIN = "runtime aktif dil → varsayılan dil → ANAHTARIN KENDİSİ zincirini izler (zeyrek-frontend/packages/ng-apps-screen-builder/src/translation.functions.ts, resolveTranslation); ilk iki halka boşsa ekrana çeviri değil ham anahtar basılır";
59
+ const WHY_SILENT = "Backend dil kodunu yalnız 184 kodluk ISO sözlüğüne göre doğruluyor (TR-001, ProjectConstants.SupportedLanguages), projenin kendi languages listesine göre DEĞİL — yazma bu yüzden 200 dönüyor ve hiçbir katman uyarmıyor.";
60
+ function quoted(codes) {
61
+ return codes.map(code => `'${code}'`).join(', ');
62
+ }
63
+ /**
64
+ * TR-002 — çeviri yazmasını projenin dil listesine bağlar.
65
+ *
66
+ * Ölçüm: 2026-09-09 1. koşu `S-15`. 15 anahtar × 2 dil yazıldı (200), 15 prop `translation` tipine
67
+ * bağlandı (200), şema doğru kuruldu — ama proje `languages` taşımadığı için runtime her etiketi
68
+ * ham anahtar olarak çizdi. Koşunun en pahalı sessiz düşmesiydi: yalnız ekrana bakınca görülüyor,
69
+ * rubrikte yanlış "tam" puanına mal oldu. Kontrol yazmayı DURDURMAZ (satırlar geçerli ve sonradan
70
+ * çözülebilir hale gelir), yüksek sesle söyler.
71
+ */
72
+ export function translationLanguageFindings(state, writtenCodes, projectSlug) {
73
+ const codes = [...new Set(writtenCodes)].sort();
74
+ // Proje okunamadıysa ya da çağrı yalnız silme yapıyorsa söylenecek bir şey yok.
75
+ if (!state || codes.length === 0)
76
+ return [];
77
+ const guide = 'ngapps://guides/translation';
78
+ if (state.languages.length === 0) {
79
+ return [{
80
+ ruleId: 'TR-002',
81
+ severity: 'warn',
82
+ message: `'${projectSlug}' projesinde 'languages' listesi TANIMLI DEĞİL: bu yazma platformda başarılı olacak ve satırlar veritabanına düşecek ama RUNTIME HİÇBİRİNİ ÇÖZEMEYECEK — ${CHAIN}. Yazılan kod: ${quoted(codes)}. Düzeltme (yazmadan önce ya da hemen sonra): project_update(id, languages: [${quoted(codes)}], defaultLanguage: '${codes[0]}'). ${WHY_SILENT}`,
83
+ guide
84
+ }];
85
+ }
86
+ const findings = [];
87
+ const undeclared = codes.filter(code => !state.languages.includes(code));
88
+ if (undeclared.length > 0) {
89
+ findings.push({
90
+ ruleId: 'TR-002',
91
+ severity: 'warn',
92
+ message: `${quoted(undeclared)} kodu '${projectSlug}' projesinin languages listesinde yok (tanımlı: ${quoted(state.languages)}): satırlar yazılır ama runtime o dile hiç geçemez, o dil seçiliyken ${CHAIN}. Düzeltme: project_update(id, languages: [${quoted([...state.languages, ...undeclared])}]). ${WHY_SILENT}`,
93
+ guide
94
+ });
95
+ }
96
+ if (state.defaultLanguage === undefined) {
97
+ findings.push({
98
+ ruleId: 'TR-002',
99
+ severity: 'warn',
100
+ message: `'${projectSlug}' projesinde languages kurulu (${quoted(state.languages)}) ama 'defaultLanguage' YOK: runtime kayıtlı bir dil seçimi bulamadığında varsayılana düşer, o da boş olduğu için aktif dil hiç seçilmez ve ilk açılışta her etiket ham anahtar olarak çizilir. Düzeltme: project_update(id, defaultLanguage: '${state.languages[0]}').`,
101
+ guide
102
+ });
103
+ }
104
+ return findings;
105
+ }
@@ -0,0 +1,10 @@
1
+ export function summarizeLayout(boxes, viewport) {
2
+ const outside = (box) => box.x + box.width <= 0 || box.y + box.height <= 0 || box.x >= viewport.width || box.y >= viewport.height;
3
+ return {
4
+ nodeCount: boxes.length,
5
+ overflowing: boxes.filter(box => box.overflowX || box.overflowY).map(box => box.elementId),
6
+ outsideViewport: boxes.filter(box => box.width > 0 && box.height > 0 && outside(box)).map(box => box.elementId),
7
+ zeroSized: boxes.filter(box => box.width === 0 || box.height === 0).map(box => box.elementId),
8
+ truncated: boxes.length >= 500
9
+ };
10
+ }
@@ -0,0 +1,51 @@
1
+ import { RefResolveError } from '../platform/refs.js';
2
+ /**
3
+ * FK slug'ı TAHMİN EDİLMEZ: rehber "satırı elle kurmadan önce mutlaka entity_get ile doğrula"
4
+ * diyor, gateway de aynısını yapar — companion'ın alanları arasında `relationId` ana entity'ye
5
+ * işaret edeni arar. Eşleşme yoksa uydurulmuş bir slug'la yanlış satır yazmaktansa hata verilir.
6
+ */
7
+ export async function findLocalizationCompanion(resolver, main) {
8
+ const companionSlug = `${main.slug}_localization`;
9
+ // Bağ ÖNCE id'den okunur: ad kalıbı ('{slug}_localization') platformun ürettiği kolaylıktır,
10
+ // sözleşme değil — companion yeniden adlandırılırsa slug yolu "companion yok" der ve ajan var
11
+ // olan bir entity'yi yeniden kurmaya çalışır. `localizationEntityId` entity'nin kendi alanıdır
12
+ // (b-03) ve entity_get onu döndürür; eski platform sürümünde boş gelir, o zaman slug yoluna
13
+ // düşülür.
14
+ const linkedId = await resolver.entityRecord(main)
15
+ .then(record => {
16
+ const value = record.localizationEntityId;
17
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
18
+ })
19
+ .catch(() => undefined);
20
+ const companion = await (linkedId ? resolver.entity(linkedId) : Promise.reject(new RefResolveError('link yok')))
21
+ .catch(() => resolver.entity(companionSlug))
22
+ .catch((error) => {
23
+ // YALNIZ "yok" hâli dostane mesaja çevrilir; belirsizlik / HTTP / yetki hataları olduğu gibi
24
+ // geçer — hepsini "companion yok" diye yutmak ajana var olan bir entity'yi kurdurur.
25
+ if (error instanceof RefResolveError && /diye bir entity yok/.test(error.message)) {
26
+ throw new RefResolveError(`'${main.slug}' için companion entity yok — entity'de isLocalized alan yok ya da companion henüz kurulmadı (ne 'localizationEntityId' bağı ne de '${companionSlug}' adı bulundu). platform_guide('entity') §'Çoklu dilli alan'.`);
27
+ }
28
+ throw error;
29
+ });
30
+ const fields = await resolver.entityFields(companion);
31
+ const candidates = fields.filter(field => field.relationType !== 'none' && field.relationId === main.entityId);
32
+ const [fk] = candidates;
33
+ if (!fk) {
34
+ throw new RefResolveError(`'${companionSlug}' içinde relationId '${main.entityId}' olan bir ilişki alanı yok; FK tahmin edilmez. Alanlar: ${fields.map(field => field.slug).join(', ')}`);
35
+ }
36
+ if (candidates.length > 1) {
37
+ throw new RefResolveError(`'${companionSlug}' içinde '${main.slug}'a işaret eden birden çok ilişki alanı var (${candidates.map(field => field.slug).join(', ')}); hangisinin FK olduğu tahmin edilmez — dil satırlarını row_create ile elle yazın.`);
38
+ }
39
+ return { entityId: companion.entityId, datasourceId: companion.datasourceId, slug: companion.slug, fkSlug: fk.slug, languageSlug: 'language' };
40
+ }
41
+ export function localizationRows(companion, mainRowId, localizations) {
42
+ return Object.entries(localizations).map(([language, values]) => ({ [companion.fkSlug]: mainRowId, [companion.languageSlug]: language, ...values }));
43
+ }
44
+ /**
45
+ * Platform tanımadığı gövde alanını SESSİZCE düşürür; `X-Unmapped-Members` üretim dışı ortamda
46
+ * ilk tanınmayan alanın JSON yolunu döner. `localizations` orada görünüyorsa platform b-07 öncesi
47
+ * sürümdedir ve dil satırları hiç yazılmamıştır — gateway o zaman companion'a kendisi yazar.
48
+ */
49
+ export function platformIgnoredLocalizations(headers) {
50
+ return (headers['x-unmapped-members'] ?? '').split(/[.,\s]+/).some(segment => segment.toLowerCase() === 'localizations');
51
+ }