@appaflytech/zeyrek-ai-mcp 0.0.43 → 0.0.45
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.
- package/dist/knowledge/backendConstraints.js +86 -2
- package/dist/knowledge/componentCatalog.js +63 -26
- package/dist/knowledge/componentEvents.js +6 -0
- package/dist/knowledge/connectDataBind.js +43 -6
- package/dist/knowledge/connectDataDefinition.js +74 -3
- package/dist/knowledge/connectDataRules.js +14 -0
- package/dist/knowledge/mobileComponentCatalog.js +66 -7
- package/dist/knowledge/mobileStore.js +61 -2
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileRules.js +2 -1
- package/dist/knowledge/platform/mobileTabs.js +31 -2
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +195 -4
- package/dist/knowledge/textHooks.js +434 -0
- package/dist/platform/capture/capture.js +1 -1
- package/dist/platform/capture/readiness.js +4 -0
- package/dist/platform/errors.js +22 -2
- package/dist/prompts/mobilePublish.js +21 -0
- package/dist/resources/examples/flow-biometric-confirm.json +199 -0
- package/dist/resources/examples/microfunction-node-dependencies.json +19 -0
- package/dist/resources/guides/access.md +3 -3
- package/dist/resources/guides/component.md +20 -4
- package/dist/resources/guides/deployment.md +98 -0
- package/dist/resources/guides/entity.md +1 -1
- package/dist/resources/guides/expressions.md +5 -1
- package/dist/resources/guides/external-component.md +6 -4
- package/dist/resources/guides/merge.md +81 -4
- package/dist/resources/guides/microfunction.md +104 -9
- package/dist/resources/guides/microservice.md +34 -5
- package/dist/resources/guides/mobile-connect-data.md +70 -0
- package/dist/resources/guides/mobile-flow.md +64 -0
- package/dist/resources/guides/mobile-publish.md +161 -16
- package/dist/resources/guides/mobile-steps.md +17 -0
- package/dist/resources/guides/mobile.md +41 -23
- package/dist/resources/guides/page.md +11 -5
- package/dist/resources/guides/translation.md +95 -0
- package/dist/resources/knowledge.js +3 -1
- package/dist/resources/prompts/mobile-publish-prepare.md +89 -0
- package/dist/resources/schema-fingerprint.json +4 -4
- package/dist/resources/schemas/ApplyMicrofunctionArchiveChangesCommand.json +76 -0
- package/dist/resources/schemas/ConnectDataDescribeDto.json +44 -0
- package/dist/resources/schemas/ConnectDataResultDto.json +10 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +8 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +0 -4
- package/dist/resources/schemas/MobileStorePreflightScope.json +13 -0
- package/dist/resources/schemas/StartMobileBuildCommand.json +35 -1
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +8 -0
- package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +75 -0
- package/dist/server.js +2 -0
- package/dist/tools/common.js +6 -0
- package/dist/tools/components.js +19 -6
- package/dist/tools/connectData.js +9 -4
- package/dist/tools/deployments.js +1 -1
- package/dist/tools/designSchemaPatch.js +16 -4
- package/dist/tools/environments.js +3 -3
- package/dist/tools/files.js +1 -1
- package/dist/tools/flows.js +29 -3
- package/dist/tools/layouts.js +32 -12
- package/dist/tools/merges.js +8 -8
- package/dist/tools/microfunctions.js +53 -3
- package/dist/tools/mobileStore.js +83 -21
- package/dist/tools/pages.js +59 -24
- package/dist/tools/platformInfo.js +4 -2
- package/dist/tools/textHookGuard.js +116 -0
- package/package.json +2 -2
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
// Okunduğunda ekrandaki yazıyı veren alanlar ve öznitelikler: bunlarla karşılaştırma her metinde kancadır.
|
|
2
|
+
// `statusText` bilerek yok: XHR / fetch yanıtının HTTP durum metnidir ('Not Found'), arayüz yazısı değil.
|
|
3
|
+
const DISPLAY_PROPS = 'title|label|text|textContent|innerText|innerHTML|outerText|nodeValue|caption|placeholder|buttonText|headerText|displayText|tooltip|ariaLabel';
|
|
4
|
+
const DISPLAY_ATTRS = 'aria-label|title|placeholder|alt';
|
|
5
|
+
const displayMember = new RegExp(`(?:\\.|\\?\\.)\\s*(?:${DISPLAY_PROPS})\\s*$`);
|
|
6
|
+
const displayBare = new RegExp(`^(?:${DISPLAY_PROPS})$`);
|
|
7
|
+
const displayAttribute = new RegExp(`getAttribute\\s*\\(\\s*['"](?:${DISPLAY_ATTRS})['"]\\s*\\)\\s*$`);
|
|
8
|
+
// Adres parametresi bir koddur — dili ne olursa olsun (`mod === 'düzenle'`) görünen yazı değildir.
|
|
9
|
+
const codeSource = /\bqueryParams\b/;
|
|
10
|
+
const normalizingCall = /(?:\?\.|\.)\s*(?:trim|trimStart|trimEnd|toLowerCase|toUpperCase|toLocaleLowerCase|toLocaleUpperCase|normalize)\s*\([^()]*\)\s*$/;
|
|
11
|
+
// `=` çevresinde boşluk yok: `const [title = 'Başlık'] = args` dizi yıkımı seçici sanılmasın.
|
|
12
|
+
const selectorPattern = new RegExp(`\\[\\s*(?:${DISPLAY_ATTRS})[~|^$*]?=(?:"([^"\\n]*)"|'([^'\\n]*)'|([^\\]"'\\s]+))\\s*[is]?\\s*\\]|:(?:contains|has-text)\\(\\s*(?:"([^"\\n]*)"|'([^'\\n]*)'|([^)"'\\n]+))\\s*\\)`, 'g');
|
|
13
|
+
// Q(,Q)* biçimi: önceki `(?:\s*Q\s*,?)+` boşlukla ayrılmış dizelerde üstel geri izliyordu (n=20 → 13 sn, inceleme 2026-10-16).
|
|
14
|
+
const QUOTED = `(?:'(?:[^'\\\\\\n]|\\\\.)*'|"(?:[^"\\\\\\n]|\\\\.)*")`;
|
|
15
|
+
const arrayMembership = new RegExp(`\\[\\s*(${QUOTED}(?:\\s*,\\s*${QUOTED})*)\\s*,?\\s*\\]\\s*(?:\\?\\.|\\.)\\s*(?:includes|indexOf)\\s*\\(`, 'g');
|
|
16
|
+
const quoted = /'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"/g;
|
|
17
|
+
const regexTest = /(^|[=(,:!&|?;{}\s])\/((?:[^/\\\n]|\\.)+)\/([gimsuy]*)\s*\.\s*test\s*\(/g;
|
|
18
|
+
const regexMatch = /(?:\?\.|\.)\s*(?:match|search)\s*\(\s*\/((?:[^/\\\n]|\\.)+)\/[gimsuy]*/g;
|
|
19
|
+
const scriptBody = /<script\b[^>]*>([\s\S]*?)<\/script>/gi;
|
|
20
|
+
// Karşılaştırılan tek kelime biçimce kod gibi görünüyorsa ama dilin / tarayıcının kendi adıysa aday değildir.
|
|
21
|
+
const LANGUAGE_WORDS = new Set([
|
|
22
|
+
'string', 'number', 'boolean', 'object', 'function', 'undefined', 'symbol', 'bigint',
|
|
23
|
+
'Enter', 'Escape', 'Esc', 'Tab', 'Backspace', 'Delete', 'Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown',
|
|
24
|
+
'none', 'block', 'flex', 'grid', 'inline', 'hidden', 'visible', 'auto', 'true', 'false', 'null'
|
|
25
|
+
]);
|
|
26
|
+
function hasForeignLetter(text) {
|
|
27
|
+
return /[^\x00-\x7F]/.test(text.replace(/[^\p{L}]/gu, ''));
|
|
28
|
+
}
|
|
29
|
+
/** En az iki harf: Türkçe arama / slug normalleştirmesindeki tek harf (`case 'ş': return 's'`) yazı değildir. */
|
|
30
|
+
export function looksLikeDisplayText(value) {
|
|
31
|
+
const text = value.trim();
|
|
32
|
+
if ((text.match(/\p{L}/gu) ?? []).length < 2)
|
|
33
|
+
return false;
|
|
34
|
+
return /\s/.test(text) || hasForeignLetter(text);
|
|
35
|
+
}
|
|
36
|
+
/** Görünen alanla karşılaştırılan metin söz gibi olmalı: ikon adı ('download') ve karakter sınıfı ('a-zA-Z0-9') değil. */
|
|
37
|
+
function looksLikeWording(value) {
|
|
38
|
+
const text = value.trim();
|
|
39
|
+
return looksLikeDisplayText(text) || /^\p{Lu}\p{Ll}/u.test(text);
|
|
40
|
+
}
|
|
41
|
+
function isWordCandidate(value) {
|
|
42
|
+
return /^[A-Za-z][A-Za-z0-9_-]+$/.test(value) && !LANGUAGE_WORDS.has(value);
|
|
43
|
+
}
|
|
44
|
+
function readsDisplayText(operand) {
|
|
45
|
+
let expression = operand.trim();
|
|
46
|
+
while (normalizingCall.test(expression))
|
|
47
|
+
expression = expression.replace(normalizingCall, '').trim();
|
|
48
|
+
return displayMember.test(expression) || displayBare.test(expression) || displayAttribute.test(expression);
|
|
49
|
+
}
|
|
50
|
+
/** `${` açılışından sonra eşleşen `}`'nin konumu; içteki dize ve iç içe şablon atlanır. */
|
|
51
|
+
function interpolationEnd(code, start) {
|
|
52
|
+
let depth = 0;
|
|
53
|
+
let index = start;
|
|
54
|
+
while (index < code.length) {
|
|
55
|
+
const char = code[index];
|
|
56
|
+
if (char === "'" || char === '"' || char === '`') {
|
|
57
|
+
let cursor = index + 1;
|
|
58
|
+
while (cursor < code.length && code[cursor] !== char)
|
|
59
|
+
cursor += code[cursor] === '\\' ? 2 : 1;
|
|
60
|
+
index = cursor + 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (char === '{')
|
|
64
|
+
depth++;
|
|
65
|
+
else if (char === '}') {
|
|
66
|
+
if (depth === 0)
|
|
67
|
+
return index;
|
|
68
|
+
depth--;
|
|
69
|
+
}
|
|
70
|
+
index++;
|
|
71
|
+
}
|
|
72
|
+
return code.length;
|
|
73
|
+
}
|
|
74
|
+
function scanLiterals(code) {
|
|
75
|
+
const literals = [];
|
|
76
|
+
let index = 0;
|
|
77
|
+
while (index < code.length) {
|
|
78
|
+
const char = code[index];
|
|
79
|
+
// `\/` kaçışlı bölü (düzenli ifade `/^https?:\/\//`) yorum başlangıcı değildir.
|
|
80
|
+
if (char === '/' && code[index + 1] === '/' && code[index - 1] !== '\\') {
|
|
81
|
+
const newline = code.indexOf('\n', index);
|
|
82
|
+
index = newline < 0 ? code.length : newline;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (char === '/' && code[index + 1] === '*') {
|
|
86
|
+
const close = code.indexOf('*/', index + 2);
|
|
87
|
+
index = close < 0 ? code.length : close + 2;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (char !== "'" && char !== '"' && char !== '`') {
|
|
91
|
+
index++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
let cursor = index + 1;
|
|
95
|
+
let value = '';
|
|
96
|
+
const interpolations = [];
|
|
97
|
+
while (cursor < code.length && code[cursor] !== char) {
|
|
98
|
+
if (code[cursor] === '\\') {
|
|
99
|
+
value += code[cursor + 1] ?? '';
|
|
100
|
+
cursor += 2;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (char !== '`' && code[cursor] === '\n')
|
|
104
|
+
break;
|
|
105
|
+
if (char === '`' && code[cursor] === '$' && code[cursor + 1] === '{') {
|
|
106
|
+
const end = interpolationEnd(code, cursor + 2);
|
|
107
|
+
interpolations.push({ start: cursor + 2, end });
|
|
108
|
+
cursor = end + 1;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
value += code[cursor];
|
|
112
|
+
cursor++;
|
|
113
|
+
}
|
|
114
|
+
if (code[cursor] !== char) {
|
|
115
|
+
index = cursor;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
literals.push({ value, start: index, end: cursor + 1, ...(interpolations.length > 0 ? { interpolations } : {}) });
|
|
119
|
+
index = cursor + 1;
|
|
120
|
+
}
|
|
121
|
+
return literals;
|
|
122
|
+
}
|
|
123
|
+
/** Dize içeriklerini aynı uzunlukta dolguyla örter: işlenen sınırları aranırken tırnak içi ayraç sayılmasın. */
|
|
124
|
+
function maskLiterals(code, literals) {
|
|
125
|
+
let masked = '';
|
|
126
|
+
let last = 0;
|
|
127
|
+
for (const literal of literals) {
|
|
128
|
+
masked += code.slice(last, literal.start + 1) + '_'.repeat(Math.max(0, literal.end - literal.start - 2));
|
|
129
|
+
last = literal.end - 1;
|
|
130
|
+
}
|
|
131
|
+
return masked + code.slice(last);
|
|
132
|
+
}
|
|
133
|
+
const OPERAND_DELIMITER = /[;,?:&|!=<>+\-*%\s]/;
|
|
134
|
+
function leftOperandStart(masked, end) {
|
|
135
|
+
let depth = 0;
|
|
136
|
+
for (let index = end - 1; index >= 0; index--) {
|
|
137
|
+
const char = masked[index] ?? '';
|
|
138
|
+
if (char === ')' || char === ']' || char === '}')
|
|
139
|
+
depth++;
|
|
140
|
+
else if (char === '(' || char === '[' || char === '{') {
|
|
141
|
+
if (depth === 0)
|
|
142
|
+
return index + 1;
|
|
143
|
+
depth--;
|
|
144
|
+
}
|
|
145
|
+
else if (depth === 0 && OPERAND_DELIMITER.test(char) && !(char === '?' && masked[index + 1] === '.'))
|
|
146
|
+
return index + 1;
|
|
147
|
+
}
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
function rightOperandEnd(masked, start) {
|
|
151
|
+
let depth = 0;
|
|
152
|
+
for (let index = start; index < masked.length; index++) {
|
|
153
|
+
const char = masked[index] ?? '';
|
|
154
|
+
if (char === '(' || char === '[' || char === '{')
|
|
155
|
+
depth++;
|
|
156
|
+
else if (char === ')' || char === ']' || char === '}') {
|
|
157
|
+
if (depth === 0)
|
|
158
|
+
return index;
|
|
159
|
+
depth--;
|
|
160
|
+
}
|
|
161
|
+
else if (depth === 0 && OPERAND_DELIMITER.test(char) && !(char === '?' && masked[index + 1] === '.'))
|
|
162
|
+
return index;
|
|
163
|
+
}
|
|
164
|
+
return masked.length;
|
|
165
|
+
}
|
|
166
|
+
function skipSpaceBack(text, index) {
|
|
167
|
+
let cursor = index;
|
|
168
|
+
while (cursor > 0 && /\s/.test(text[cursor - 1] ?? ''))
|
|
169
|
+
cursor--;
|
|
170
|
+
return cursor;
|
|
171
|
+
}
|
|
172
|
+
function skipSpaceForward(text, index) {
|
|
173
|
+
let cursor = index;
|
|
174
|
+
while (cursor < text.length && /\s/.test(text[cursor] ?? ''))
|
|
175
|
+
cursor++;
|
|
176
|
+
return cursor;
|
|
177
|
+
}
|
|
178
|
+
function excerptOf(code, start, end) {
|
|
179
|
+
return code.slice(Math.max(0, start - 40), Math.min(code.length, end + 20)).replace(/\s+/g, ' ').trim();
|
|
180
|
+
}
|
|
181
|
+
function classify(code, text, start, end, displayRead) {
|
|
182
|
+
const excerpt = excerptOf(code, start, end);
|
|
183
|
+
if (displayRead && looksLikeWording(text))
|
|
184
|
+
return { position: start, hook: { kind: 'display-read', text, excerpt, certain: true } };
|
|
185
|
+
if (looksLikeDisplayText(text))
|
|
186
|
+
return { position: start, hook: { kind: 'literal', text, excerpt, certain: hasForeignLetter(text) } };
|
|
187
|
+
if (isWordCandidate(text))
|
|
188
|
+
return { position: start, word: text };
|
|
189
|
+
return { position: start };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Düzenli ifadenin söz kısmı: kaçışlar, nicelik ve karakter sınıfları (`[a-z]`, `[çğıöşü]`, `\d{2}`) düşer,
|
|
193
|
+
* seçenekler (`a|b`) ayrı tutulur. Tarih kalıbı `/(Z|[+-]\d\d:?\d\d)$/` ve Türkçe harf normalleştirmesi
|
|
194
|
+
* `/[çğıöşü]/` böylece söz üretmez.
|
|
195
|
+
*/
|
|
196
|
+
function regexAlternatives(body) {
|
|
197
|
+
return body
|
|
198
|
+
.replace(/\[[^\]]*\]/g, '')
|
|
199
|
+
.replace(/\\[sSdDwWbBnrt]/g, '')
|
|
200
|
+
.replace(/\{\d+(?:,\d*)?\}/g, '')
|
|
201
|
+
.replace(/\\(.)/g, '$1')
|
|
202
|
+
.replace(/[\^$()[\]{}*+?.]/g, '')
|
|
203
|
+
.split('|')
|
|
204
|
+
.map(part => part.trim())
|
|
205
|
+
.filter(part => /\p{L}/u.test(part));
|
|
206
|
+
}
|
|
207
|
+
function classifyRegex(code, body, start, end, displayRead) {
|
|
208
|
+
return regexAlternatives(body).map(part => classify(code, part, start, end, displayRead));
|
|
209
|
+
}
|
|
210
|
+
function scanCode(code, offset, source) {
|
|
211
|
+
const literals = scanLiterals(code);
|
|
212
|
+
const masked = maskLiterals(code, literals);
|
|
213
|
+
const found = [];
|
|
214
|
+
for (const literal of literals) {
|
|
215
|
+
if (literal.interpolations) {
|
|
216
|
+
for (const part of literal.interpolations)
|
|
217
|
+
found.push(...scanCode(code.slice(part.start, part.end), offset + part.start, source));
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (literal.value.trim() === '')
|
|
221
|
+
continue;
|
|
222
|
+
const beforeEnd = skipSpaceBack(masked, literal.start);
|
|
223
|
+
const before = masked.slice(Math.max(0, beforeEnd - 40), beforeEnd);
|
|
224
|
+
const afterStart = skipSpaceForward(masked, literal.end);
|
|
225
|
+
const after = masked.slice(afterStart, afterStart + 3);
|
|
226
|
+
const operatorBefore = /(?:!==|===|!=|==)$/.exec(before);
|
|
227
|
+
const operatorAfter = /^(?:!==|===|!=|==)/.exec(after);
|
|
228
|
+
const method = /(?:\?\.|\.)\s*(?:includes|startsWith|endsWith|indexOf|lastIndexOf)\s*\($/.exec(before);
|
|
229
|
+
let operand;
|
|
230
|
+
if (operatorBefore) {
|
|
231
|
+
const operandEnd = skipSpaceBack(masked, beforeEnd - operatorBefore[0].length);
|
|
232
|
+
operand = code.slice(leftOperandStart(masked, operandEnd), operandEnd);
|
|
233
|
+
}
|
|
234
|
+
else if (operatorAfter) {
|
|
235
|
+
const operandStart = skipSpaceForward(masked, afterStart + operatorAfter[0].length);
|
|
236
|
+
operand = code.slice(operandStart, rightOperandEnd(masked, operandStart));
|
|
237
|
+
}
|
|
238
|
+
else if (method) {
|
|
239
|
+
const receiverEnd = beforeEnd - method[0].length;
|
|
240
|
+
operand = code.slice(leftOperandStart(masked, receiverEnd), receiverEnd);
|
|
241
|
+
}
|
|
242
|
+
else if (!/\bcase$/.test(before) && !/\bnew\s+RegExp\s*\($/.test(before)) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (operand !== undefined && codeSource.test(operand))
|
|
246
|
+
continue;
|
|
247
|
+
const located = classify(code, literal.value, literal.start, literal.end, operand !== undefined && readsDisplayText(operand));
|
|
248
|
+
found.push({ ...located, position: offset + located.position, ...(located.hook ? { hook: { ...located.hook, excerpt: excerptOf(source, offset + literal.start, offset + literal.end) } } : {}) });
|
|
249
|
+
}
|
|
250
|
+
for (const match of masked.matchAll(arrayMembership)) {
|
|
251
|
+
const list = match[1] ?? '';
|
|
252
|
+
const listStart = (match.index ?? 0) + match[0].indexOf(list);
|
|
253
|
+
for (const item of code.slice(listStart, listStart + list.length).matchAll(quoted)) {
|
|
254
|
+
const value = item[1] ?? item[2] ?? '';
|
|
255
|
+
const start = listStart + (item.index ?? 0);
|
|
256
|
+
const located = classify(source, value, offset + start, offset + start + item[0].length, false);
|
|
257
|
+
found.push(located);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Örtülmüş metinde aranır: şablon dizesinin `${…}` gövdesindeki ifade özyinelemede bir kez sayılsın.
|
|
261
|
+
for (const match of masked.matchAll(regexTest)) {
|
|
262
|
+
const lead = match[1] ?? '';
|
|
263
|
+
const bodyStart = (match.index ?? 0) + lead.length + 1;
|
|
264
|
+
const body = code.slice(bodyStart, bodyStart + (match[2] ?? '').length);
|
|
265
|
+
const argumentStart = (match.index ?? 0) + match[0].length;
|
|
266
|
+
const argument = code.slice(argumentStart, rightOperandEnd(masked, argumentStart));
|
|
267
|
+
found.push(...classifyRegex(source, body, offset + bodyStart - 1, offset + bodyStart + body.length + 1, readsDisplayText(argument)));
|
|
268
|
+
}
|
|
269
|
+
for (const match of masked.matchAll(regexMatch)) {
|
|
270
|
+
const methodStart = match.index ?? 0;
|
|
271
|
+
const receiver = code.slice(leftOperandStart(masked, methodStart), methodStart);
|
|
272
|
+
const bodyEnd = methodStart + match[0].length - (match[0].length - match[0].lastIndexOf('/'));
|
|
273
|
+
const body = code.slice(bodyEnd - (match[1] ?? '').length, bodyEnd);
|
|
274
|
+
found.push(...classifyRegex(source, body, offset + methodStart, offset + methodStart + match[0].length, readsDisplayText(receiver)));
|
|
275
|
+
}
|
|
276
|
+
return found;
|
|
277
|
+
}
|
|
278
|
+
function scanSelectors(source) {
|
|
279
|
+
const found = [];
|
|
280
|
+
for (const match of source.matchAll(selectorPattern)) {
|
|
281
|
+
const value = (match.slice(1).find(group => group !== undefined) ?? '').trim();
|
|
282
|
+
if (value === '' || value.includes('{{') || value.includes('${'))
|
|
283
|
+
continue;
|
|
284
|
+
const start = match.index ?? 0;
|
|
285
|
+
// Etiket özniteliği görünen alandır: söz gibi değer kesin kanca, kod gibi tek kelime TR-006 adayı.
|
|
286
|
+
if (looksLikeWording(value))
|
|
287
|
+
found.push({ position: start, hook: { kind: 'selector', text: value, excerpt: excerptOf(source, start, start + match[0].length), certain: true } });
|
|
288
|
+
else if (isWordCandidate(value))
|
|
289
|
+
found.push({ position: start, word: value });
|
|
290
|
+
}
|
|
291
|
+
return found;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* html içeriğinde (`<` ile başlayan dize) kod yalnız `<script>` gövdesidir: düz metindeki kesme işareti
|
|
295
|
+
* ("Kullanıcı'nın") dize başlangıcı sanılıp betiğin gerçek dizelerini kaydırmasın. Etiket kuran akış kodu
|
|
296
|
+
* (`'<div>' + x`) `<` ile başlamadığı için kod olarak taranır.
|
|
297
|
+
*/
|
|
298
|
+
function locate(source) {
|
|
299
|
+
const scripts = /^\s*</.test(source) ? [...source.matchAll(scriptBody)] : [];
|
|
300
|
+
const code = /^\s*</.test(source)
|
|
301
|
+
? scripts.flatMap(match => scanCode(match[1] ?? '', (match.index ?? 0) + match[0].indexOf(match[1] ?? ''), source))
|
|
302
|
+
: scanCode(source, 0, source);
|
|
303
|
+
return [...code, ...scanSelectors(source)].sort((left, right) => left.position - right.position);
|
|
304
|
+
}
|
|
305
|
+
export function findTextHooks(source) {
|
|
306
|
+
return locate(source).flatMap(entry => entry.hook ? [entry.hook] : []);
|
|
307
|
+
}
|
|
308
|
+
export function comparedWords(source) {
|
|
309
|
+
return locate(source).flatMap(entry => entry.word ? [entry.word] : []);
|
|
310
|
+
}
|
|
311
|
+
function collectStrings(value, into = []) {
|
|
312
|
+
if (typeof value === 'string')
|
|
313
|
+
into.push(value);
|
|
314
|
+
else if (Array.isArray(value))
|
|
315
|
+
for (const entry of value)
|
|
316
|
+
collectStrings(entry, into);
|
|
317
|
+
else if (value && typeof value === 'object')
|
|
318
|
+
for (const entry of Object.values(value))
|
|
319
|
+
collectStrings(entry, into);
|
|
320
|
+
return into;
|
|
321
|
+
}
|
|
322
|
+
function colorMapValues(value, into = []) {
|
|
323
|
+
if (Array.isArray(value)) {
|
|
324
|
+
for (const entry of value)
|
|
325
|
+
colorMapValues(entry, into);
|
|
326
|
+
}
|
|
327
|
+
else if (value && typeof value === 'object') {
|
|
328
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
329
|
+
if (key === 'colorMap' && Array.isArray(entry)) {
|
|
330
|
+
for (const item of entry) {
|
|
331
|
+
const mapped = item && typeof item === 'object' ? item.value : undefined;
|
|
332
|
+
if (typeof mapped === 'string' && mapped.trim() !== '')
|
|
333
|
+
into.push(mapped);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
colorMapValues(entry, into);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return into;
|
|
342
|
+
}
|
|
343
|
+
export function textHooksIn(value) {
|
|
344
|
+
return [
|
|
345
|
+
...collectStrings(value).flatMap(findTextHooks),
|
|
346
|
+
...colorMapValues(value)
|
|
347
|
+
.filter(looksLikeDisplayText)
|
|
348
|
+
.map(text => ({ kind: 'color-map', text, excerpt: `colorMap value: '${text}'`, certain: hasForeignLetter(text) }))
|
|
349
|
+
];
|
|
350
|
+
}
|
|
351
|
+
export function comparedWordsIn(value) {
|
|
352
|
+
return [...collectStrings(value).flatMap(comparedWords), ...colorMapValues(value).filter(isWordCandidate)];
|
|
353
|
+
}
|
|
354
|
+
function multisetDifference(before, after, key) {
|
|
355
|
+
const remaining = new Map();
|
|
356
|
+
for (const entry of before)
|
|
357
|
+
remaining.set(key(entry), (remaining.get(key(entry)) ?? 0) + 1);
|
|
358
|
+
return after.filter(entry => {
|
|
359
|
+
const left = remaining.get(key(entry)) ?? 0;
|
|
360
|
+
if (left === 0)
|
|
361
|
+
return true;
|
|
362
|
+
remaining.set(key(entry), left - 1);
|
|
363
|
+
return false;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
/** Kayıtta aynı kanca kaç kez duruyorsa o kadarı eskidir; fazlası bu yazımla gelmiştir. Konuma bakmaz: adım ve düğüm sırası değişebilir. */
|
|
367
|
+
export function addedHooks(before, after) {
|
|
368
|
+
return multisetDifference(before, after, hook => `${hook.kind}\u0000${hook.text}`);
|
|
369
|
+
}
|
|
370
|
+
export function addedWords(before, after) {
|
|
371
|
+
return multisetDifference(before, after, word => word);
|
|
372
|
+
}
|
|
373
|
+
function findNode(tree, elementId) {
|
|
374
|
+
if (Array.isArray(tree)) {
|
|
375
|
+
for (const entry of tree) {
|
|
376
|
+
const found = findNode(entry, elementId);
|
|
377
|
+
if (found)
|
|
378
|
+
return found;
|
|
379
|
+
}
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
if (!tree || typeof tree !== 'object')
|
|
383
|
+
return undefined;
|
|
384
|
+
const node = tree;
|
|
385
|
+
return node.elementId === elementId ? node : findNode(node.children, elementId);
|
|
386
|
+
}
|
|
387
|
+
const OP_AREAS = { set_validations: 'validations', set_appearance: 'appearance', set_states: 'states', set_block: 'blocks' };
|
|
388
|
+
/**
|
|
389
|
+
* Micro-op yamasında "kayıtta duran" yalnız op'un EZDİĞİ değerdir: `replace_prop` / `set_prop_type` / `set_binding` için
|
|
390
|
+
* hedef düğümün o prop'u, alan op'ları için düğümün o alanı; `add_component` hiçbir şeyi ezmez. Bütün ağaç taban
|
|
391
|
+
* alınsaydı başka düğümde duran aynı metin (qm'de aynı kanca onlarca düğümde) yeni kancayı eski gösterirdi (inceleme 2026-10-16).
|
|
392
|
+
*/
|
|
393
|
+
export function overwrittenValues(tree, ops) {
|
|
394
|
+
return ops.flatMap(op => {
|
|
395
|
+
if (typeof op.target !== 'string' || op.op === 'add_component')
|
|
396
|
+
return [];
|
|
397
|
+
const node = findNode(tree, op.target);
|
|
398
|
+
if (!node)
|
|
399
|
+
return [];
|
|
400
|
+
if (typeof op.prop === 'string')
|
|
401
|
+
return [node.props?.[op.prop]];
|
|
402
|
+
const area = OP_AREAS[String(op.op)];
|
|
403
|
+
return area ? [node[area]] : [];
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const DISPLAY_KEYS = new Set(['label', 'title', 'text', 'placeholder', 'description', 'header', 'caption', 'buttonText', 'emptyText', 'tooltip', 'subtitle', 'helperText']);
|
|
407
|
+
function addDisplayText(value, into) {
|
|
408
|
+
if (typeof value === 'string') {
|
|
409
|
+
if (value.trim() !== '' && !value.includes('{{'))
|
|
410
|
+
into.add(value.trim());
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const wrapped = value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
414
|
+
if (wrapped && (wrapped.type === 'static' || wrapped.type === 'translation') && typeof wrapped.data === 'string')
|
|
415
|
+
addDisplayText(wrapped.data, into);
|
|
416
|
+
}
|
|
417
|
+
/** Barındırıcının (sayfa / layout / widget ağacı) görünen yazıları: statik metin ve çeviri bağının tasarım yedeği (`data`). Kod bağı okunmaz. */
|
|
418
|
+
export function displayTextsIn(tree, into = new Set()) {
|
|
419
|
+
if (Array.isArray(tree)) {
|
|
420
|
+
for (const entry of tree)
|
|
421
|
+
displayTextsIn(entry, into);
|
|
422
|
+
}
|
|
423
|
+
else if (tree && typeof tree === 'object') {
|
|
424
|
+
const record = tree;
|
|
425
|
+
if (record.type === 'code')
|
|
426
|
+
return into;
|
|
427
|
+
for (const [key, value] of Object.entries(record)) {
|
|
428
|
+
if (DISPLAY_KEYS.has(key))
|
|
429
|
+
addDisplayText(value, into);
|
|
430
|
+
displayTextsIn(value, into);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return into;
|
|
434
|
+
}
|
|
@@ -431,7 +431,7 @@ export class StudioScreenshotter {
|
|
|
431
431
|
if (state !== undefined) {
|
|
432
432
|
await page.waitForTimeout(flowSettleGraceMs);
|
|
433
433
|
const confirmed = parseReadiness(await page.evaluate(readinessScript).catch(() => undefined));
|
|
434
|
-
if (confirmed !== undefined && !confirmed.skeleton && confirmed.href === snapshot.href && (confirmed.activity === null || confirmed.activity.running === 0)) {
|
|
434
|
+
if (confirmed !== undefined && !confirmed.skeleton && confirmed.href === snapshot.href && (confirmed.activity === null || confirmed.activity.running === 0 || (state === 'awaiting-user' && confirmed.awaitingUser))) {
|
|
435
435
|
return { state, waitedMs: Date.now() - startedAt, snapshot: confirmed };
|
|
436
436
|
}
|
|
437
437
|
previousKey = undefined;
|
|
@@ -22,6 +22,7 @@ export const readinessScript = `(function () { /* readinessSnapshot */
|
|
|
22
22
|
skeleton: !!document.querySelector('.view-detail > .bx--skeleton__placeholder'),
|
|
23
23
|
viewDetail: !!document.querySelector('.view-detail'),
|
|
24
24
|
errorState: !!document.querySelector('.view-detail__error, .view-detail .error-page'),
|
|
25
|
+
awaitingUser: !!document.querySelector('[data-element-id="preview-biometric"]'),
|
|
25
26
|
readyState: String(document.readyState),
|
|
26
27
|
href: String(location.href)
|
|
27
28
|
};
|
|
@@ -46,6 +47,7 @@ export function parseReadiness(raw) {
|
|
|
46
47
|
skeleton: value.skeleton === true,
|
|
47
48
|
viewDetail: value.viewDetail === true,
|
|
48
49
|
errorState: value.errorState === true,
|
|
50
|
+
awaitingUser: value.awaitingUser === true,
|
|
49
51
|
readyState: String(value.readyState ?? ''),
|
|
50
52
|
href: value.href
|
|
51
53
|
};
|
|
@@ -79,6 +81,8 @@ export function decideReadiness(snapshot, quiet, completedBefore, baselineUrl) {
|
|
|
79
81
|
return 'ready';
|
|
80
82
|
if (idle && snapshot.runtimeNodes > 0 && quiet >= quietPolls)
|
|
81
83
|
return 'ready-no-flows';
|
|
84
|
+
if (snapshot.awaitingUser && snapshot.runtimeNodes > 0 && quiet >= quietPolls)
|
|
85
|
+
return 'awaiting-user';
|
|
82
86
|
if (activity === null && snapshot.runtimeNodes > 0 && quiet >= legacyQuietPolls)
|
|
83
87
|
return 'ready-no-counter';
|
|
84
88
|
if (idle && snapshot.errorState && quiet >= quietPolls)
|
package/dist/platform/errors.js
CHANGED
|
@@ -73,6 +73,15 @@ function mergeHint(path) {
|
|
|
73
73
|
return '';
|
|
74
74
|
return " Üç sebepten biri: çözülmemiş çakışma, ya da plan alındığından beri DALIN veya development’ın değişmesi (MergePlanStaleException) — ikincisinde planın taşıdığı liste eskidir ve uygulansaydı sonradan yapılan iş sessizce düşerdi. merge_get ile planı yeniden okuyun (isStale), bayatsa merge_refresh ile tazeleyin — yeni plan açmanız gerekmez. Aynı merge_apply çağrısını tekrarlamak sonucu değiştirmez.";
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* `POST /microfunctions/{id}/archive/changes` iyimser kilittir: istemcinin gönderdiği expectedHash
|
|
78
|
+
* kaydın hash'i değilse hiçbir şey yazılmaz. Aynı gövdeyi tekrar göndermek sonucu değiştirmez.
|
|
79
|
+
*/
|
|
80
|
+
function microfunctionArchiveHint(code) {
|
|
81
|
+
if (code !== 'MicrofunctionArchiveConflictException')
|
|
82
|
+
return '';
|
|
83
|
+
return ' Arşiv siz okuduktan sonra değişti (expectedHash güncel değil) ya da iskelet istenen kayıtta arşiv zaten var. microfunction_files_list ile güncel hash\'i ve girdileri yeniden okuyun, işlemleri ona göre kurup tekrar gönderin; aynı çağrıyı tekrarlamak sonucu değiştirmez.';
|
|
84
|
+
}
|
|
76
85
|
function mergeRevertHint(code) {
|
|
77
86
|
if (code === 'MergeRevertWindowExpiredException') {
|
|
78
87
|
return " Geri alma penceresi doldu: pencere kapanınca merge öncesi anlık görüntü de silinir, bu yüzden plan artık geri alınamaz. merge_list status:'applied' ile pencere içindeki bir plan seçin.";
|
|
@@ -85,6 +94,17 @@ function mergeRevertHint(code) {
|
|
|
85
94
|
}
|
|
86
95
|
return '';
|
|
87
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Şablondan kurulup güncellemeleri alan projede dal ve merge yazmaları kapalıdır (backend
|
|
99
|
+
* `CheckBranchWriteAllowedBehavior`, ADR 0022, MRG-018). Ret kalıcıdır: ajan bunu dalı ya da
|
|
100
|
+
* planı düzelterek aşamaz, değişiklik şablonun kaynak projesinden gelir. Backend 400 döner —
|
|
101
|
+
* 409 olsaydı `mergeHint` "bayat plan, merge_refresh" diye yanlış yöne gönderirdi.
|
|
102
|
+
*/
|
|
103
|
+
function templateSubscriptionHint(code) {
|
|
104
|
+
if (code !== 'CantBranchTemplateSubscribedProjectException')
|
|
105
|
+
return '';
|
|
106
|
+
return ' Bu proje şablon güncellemelerini alıyor; dal açma, dal silme ve merge planının hiçbir adımı bu projede yapılamaz. Değişikliği şablonun kaynak projesinde yapın, template_create ile yeni sürüm yayımlayın, bu projede template_check_updates → template_receive_updates ile alın. Aynı çağrıyı tekrarlamak sonucu değiştirmez.';
|
|
107
|
+
}
|
|
88
108
|
/**
|
|
89
109
|
* İçe aktarma zinciri dört çağrıdır ve her hata farklı bir adıma geri gönderir: plan süresi
|
|
90
110
|
* dolduysa analizden, anahtar benzersiz değilse plan gövdesinden, hata oranı yüksekse dosyanın
|
|
@@ -375,7 +395,7 @@ export function mapPlatformError(status, payload, context) {
|
|
|
375
395
|
if (validationMessages.length > 0) {
|
|
376
396
|
return new PlatformApiError(`Doğrulama hatası (${location}): ${validationMessages.join(' ; ')}. Gövde şeması için ilgili ngapps://schemas kaynağına ya da mevcut bir kaydın *_get çıktısına bakın.`, status, code);
|
|
377
397
|
}
|
|
378
|
-
return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${importHint(code, context.path)}${accessHint(code, apiMessage)}`, status, code);
|
|
398
|
+
return new PlatformApiError(`Geçersiz istek (${location}): ${apiMessage ?? 'API gövdeyi reddetti.'}${mergeRevertHint(code)}${templateSubscriptionHint(code)}${importHint(code, context.path)}${accessHint(code, apiMessage)}`, status, code);
|
|
379
399
|
}
|
|
380
400
|
if (status === 401) {
|
|
381
401
|
// Uygulama oturumu 401'i gateway kimliğinin değil uygulama kullanıcısının sorunudur; USERNAME/PASSWORD
|
|
@@ -441,7 +461,7 @@ export function mapPlatformError(status, payload, context) {
|
|
|
441
461
|
: '';
|
|
442
462
|
const validationMessages = collectValidationMessages(body);
|
|
443
463
|
const detail = validationMessages.length > 0 ? ` → ${validationMessages.join(' ; ')}` : '';
|
|
444
|
-
const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${processHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}${accessHint(code, apiMessage)}`, status, code);
|
|
464
|
+
const rejected = new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${processHint(code, context.path)}${bulkRowHint(code)}${importHint(code, context.path)}${versionConflictHint(code, context.path)}${mergeHint(context.path)}${accessHint(code, apiMessage)}${microfunctionArchiveHint(code)}`, status, code);
|
|
445
465
|
if (typeof body.rowIndex === 'number')
|
|
446
466
|
rejected.rowIndex = body.rowIndex;
|
|
447
467
|
return rejected;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readKnowledgeFile } from '../resources/knowledge.js';
|
|
3
|
+
export const MOBILE_PUBLISH_PROMPT = 'mobile_publish_prepare';
|
|
4
|
+
/**
|
|
5
|
+
* Yayınla 3 · Test sürümü ve 4 · Yayına hazırla adımlarının iş tarifi (ADR 0003). Metin `resources/prompts/` altındadır; burada yalnız argüman
|
|
6
|
+
* şeması ve yerleştirme durur. İkinci prompt gelince kayıt genel bir döngüye taşınır.
|
|
7
|
+
*/
|
|
8
|
+
export function renderMobilePublishPrompt(applicationId) {
|
|
9
|
+
return readKnowledgeFile('prompts', 'mobile-publish-prepare.md').replaceAll('{{applicationId}}', applicationId);
|
|
10
|
+
}
|
|
11
|
+
export function registerMobilePublishPrompt(server) {
|
|
12
|
+
server.registerPrompt(MOBILE_PUBLISH_PROMPT, {
|
|
13
|
+
title: 'Mobil uygulamayı yayına hazırla',
|
|
14
|
+
description: "Studio › Yayınla 3 · Test sürümü (kimlik ve sürüm, hesaplar, test ön kontrolü) ve 4 · Yayına hazırla (mağaza metinleri, ekran görüntüleri, yaş anketi, gizlilik adresi, beyanlar, sürüm ve fiyat) adımlarını zeyrek-ai araçlarıyla doldurur; beyanı yalnız kullanıcı onayıyla işaretler, gizli bilgi girmez, ön kontrolü dryRun koşar, test derlemesini yalnız kullanıcı isterse başlatır ve insana kalan işi listeler.",
|
|
15
|
+
argsSchema: {
|
|
16
|
+
applicationId: z.string().trim().min(1).describe("Mobil uygulamanın id'si (application_list / mobile_app_create çıktısı)")
|
|
17
|
+
}
|
|
18
|
+
}, ({ applicationId }) => ({
|
|
19
|
+
messages: [{ role: 'user', content: { type: 'text', text: renderMobilePublishPrompt(applicationId) } }]
|
|
20
|
+
}));
|
|
21
|
+
}
|