@arcadialdev/arcality 4.0.0 → 4.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadialdev/arcality",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "El primer ingeniero QA Autónomo integrado al CI/CD. Creado por Arcadial.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -641,7 +641,7 @@ async function waitForEnter(message = 'Presiona Enter para continuar...', option
641
641
  }
642
642
 
643
643
  function getMissionStartPath(missionSpec, collectionMeta = null, fallbackPath = '') {
644
- return missionSpec.pagePath || missionSpec.startAt || missionSpec.page || collectionMeta?.defaultPagePath || fallbackPath;
644
+ return missionSpec.startAt || missionSpec.page || missionSpec.pagePath || collectionMeta?.defaultPagePath || fallbackPath;
645
645
  }
646
646
 
647
647
  function listMissionLogFiles(contextDir) {
@@ -78,6 +78,19 @@ function matchesPageFilters(routeEntry, filters) {
78
78
  return haystacks.some(value => value === normalized || value.includes(normalized));
79
79
  });
80
80
  }
81
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
82
+
83
+ function isLikelyUuidFilter(filter) {
84
+ return UUID_RE.test(String(filter || '').trim());
85
+ }
86
+
87
+ function formatRouteExamples(routes, maxItems = 3) {
88
+ return routes
89
+ .slice(0, maxItems)
90
+ .map(entry => `${entry.route} -> ${entry.pagePath}`)
91
+ .join('\n- ');
92
+ }
93
+
81
94
 
82
95
  function limitEntries(entries, count) {
83
96
  if (!Number.isFinite(count) || count <= 0) return entries;
@@ -114,6 +127,14 @@ function printSummary(analysis, selectedRoutes, options, generationContext) {
114
127
 
115
128
  if (selectedRoutes.length === 0) {
116
129
  console.log('\nNo candidate routes matched the provided filters.');
130
+ const uuidFilters = (options.page || []).filter(isLikelyUuidFilter);
131
+ if (uuidFilters.length > 0) {
132
+ console.log('Tip: the current --page filter only matches route fragments or page file paths, not backend page IDs or UUIDs.');
133
+ }
134
+ if ((options.page || []).length > 0 && analysis.routes.length > 0) {
135
+ console.log('Try values such as a route (`/checkout`) or a page path fragment (`app/checkout/page.tsx`).');
136
+ console.log(`Examples:\n- ${formatRouteExamples(analysis.routes)}`);
137
+ }
117
138
  return;
118
139
  }
119
140
 
@@ -16,32 +16,32 @@ const getPid = () => process.env.ARCALITY_PROJECT_ID || '';
16
16
  const getOrgId = () => process.env.ARCALITY_ORG_ID;
17
17
  const getMissionId = () => process.env.ARCALITY_MISSION_ID || EMPTY_GUID;
18
18
 
19
- const EMPTY_GUID = '00000000-0000-0000-0000-000000000000';
20
-
21
- let configWarningLogged = false;
22
-
23
- function hasRealProjectId(pid: string): boolean {
24
- if (!pid || pid === EMPTY_GUID) return false;
25
- if (/^(local|mock)_/i.test(pid)) return false;
26
- if (pid === 'undefined' || pid === 'null') return false;
27
- return true;
28
- }
29
-
30
- function isConfigured(): boolean {
19
+ const EMPTY_GUID = '00000000-0000-0000-0000-000000000000';
20
+
21
+ let configWarningLogged = false;
22
+
23
+ function hasRealProjectId(pid: string): boolean {
24
+ if (!pid || pid === EMPTY_GUID) return false;
25
+ if (/^(local|mock)_/i.test(pid)) return false;
26
+ if (pid === 'undefined' || pid === 'null') return false;
27
+ return true;
28
+ }
29
+
30
+ function isConfigured(): boolean {
31
31
  const base = getBase();
32
32
  const key = getKey();
33
33
  const pid = getPid();
34
34
  const orgId = getOrgId();
35
35
 
36
- if (!base || !key || !hasRealProjectId(pid) || !orgId) {
37
- if (!configWarningLogged) {
38
- const missing = [];
39
- if (!base) missing.push('ARCALITY_API_URL');
40
- if (!key) missing.push('ARCALITY_API_KEY');
41
- if (!hasRealProjectId(pid)) missing.push('ARCALITY_PROJECT_ID');
36
+ if (!base || !key || !hasRealProjectId(pid) || !orgId) {
37
+ if (!configWarningLogged) {
38
+ const missing = [];
39
+ if (!base) missing.push('ARCALITY_API_URL');
40
+ if (!key) missing.push('ARCALITY_API_KEY');
41
+ if (!hasRealProjectId(pid)) missing.push('ARCALITY_PROJECT_ID');
42
42
  if (!orgId) missing.push('ARCALITY_ORG_ID');
43
43
 
44
- if (process.env.DEBUG) console.warn(`[CollectiveMemory] Service disabled. Missing: ${missing.join(', ')}`);
44
+ if (process.env.DEBUG) console.warn(`[CollectiveMemory] Service disabled. Missing: ${missing.join(', ')}`);
45
45
  configWarningLogged = true;
46
46
  }
47
47
  return false;
@@ -49,7 +49,7 @@ function isConfigured(): boolean {
49
49
  return true;
50
50
  }
51
51
 
52
- // ─── 1. REGLAS DE UI / NEGOCIO ──────────────────────────────────────────────
52
+ // ������ 1. REGLAS DE UI / NEGOCIO ��������������������������������������������������������������������������������������������
53
53
 
54
54
  export type RuleType = 'UI_UX' | 'VALIDATION' | 'NAVIGATION' | 'BUSINESS' | 'SECURITY';
55
55
  export type Severity = 'critical' | 'important' | 'suggestion';
@@ -85,7 +85,7 @@ export async function pushRule(rule: {
85
85
  }
86
86
 
87
87
  const data = await res.json();
88
- if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] Regla guardada: "${rule.title}"\x1b[0m`);
88
+ if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] �S& Regla guardada: "${rule.title}"\x1b[0m`);
89
89
  return data.id ?? null;
90
90
  } catch (err: any) {
91
91
  if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushRule error: ${err?.message}`);
@@ -93,7 +93,7 @@ export async function pushRule(rule: {
93
93
  }
94
94
  }
95
95
 
96
- // ─── 2. CATÁLOGO DE CAMPOS ──────────────────────────────────────────────────
96
+ // ������ 2. CATÁLOGO DE CAMPOS ����������������������������������������������������������������������������������������������������
97
97
 
98
98
  export type FieldType = 'email' | 'password' | 'text' | 'number' | 'select' | 'checkbox' | 'textarea' | 'date';
99
99
 
@@ -138,12 +138,12 @@ export async function pushField(field: {
138
138
  const data = await res.json();
139
139
  return data.id ?? null;
140
140
  } catch (err: any) {
141
- // Silencioso fire-and-forget, 'terminated' es esperado al finalizar el proceso
141
+ // Silencioso � fire-and-forget, 'terminated' es esperado al finalizar el proceso
142
142
  return null;
143
143
  }
144
144
  }
145
145
 
146
- // ─── 3. CONOCIMIENTO / DOCUMENTACIÓN ────────────────────────────────────────
146
+ // ������ 3. CONOCIMIENTO / DOCUMENTACI�N ��������������������������������������������������������������������������������
147
147
 
148
148
  export type DocType = 'PROCESS' | 'VALIDATION' | 'NAVIGATION' | 'COMPONENT' | 'GENERAL';
149
149
 
@@ -176,7 +176,7 @@ export async function pushKnowledge(knowledge: {
176
176
  }
177
177
 
178
178
  const data = await res.json();
179
- if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] Conocimiento guardado: "${knowledge.title}"\x1b[0m`);
179
+ if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] �S& Conocimiento guardado: "${knowledge.title}"\x1b[0m`);
180
180
  return data.id ?? null;
181
181
  } catch (err: any) {
182
182
  if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushKnowledge error: ${err?.message}`);
@@ -184,10 +184,13 @@ export async function pushKnowledge(knowledge: {
184
184
  }
185
185
  }
186
186
 
187
- // ─── 4. PROMPT EXECUTION PATTERNS ─────────────────────────────────────────────
187
+ // ������ 4. PROMPT EXECUTION PATTERNS ������������������������������������������������������������������������������������������
188
188
 
189
189
  export async function searchPromptPattern(prompt: string, limit: number = 5): Promise<any[]> {
190
- if (!isConfigured()) return [];
190
+ if (!isConfigured()) {
191
+ console.warn(`[PatternSearch] Omitido: configuraci�n incompleta para prompt-execution-patterns/search`);
192
+ return [];
193
+ }
191
194
  try {
192
195
  const apiUrl = process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1/prompt-execution-patterns/search` : `${getBase()}/prompt-execution-patterns/search`;
193
196
 
@@ -198,10 +201,11 @@ export async function searchPromptPattern(prompt: string, limit: number = 5): Pr
198
201
  limit: limit
199
202
  };
200
203
 
201
- const debugPatternSearch = process.env.DEBUG === 'true' || process.env.ARCALITY_DEBUG === 'true';
202
- if (debugPatternSearch) {
203
- console.log(`[PatternSearch] POST ${apiUrl} | prompt_chars=${prompt.length} | limit=${limit}`);
204
- }
204
+ const debugPatternSearch = process.env.DEBUG === 'true' || process.env.ARCALITY_DEBUG === 'true';
205
+ console.log(`[PatternSearch] Iniciando b�squeda | prompt_chars=${prompt.length} | limit=${limit} | project_id=${getPid() || 'missing'} | org_id=${getOrgId() || 'missing'}`);
206
+ if (debugPatternSearch) {
207
+ console.log(`[PatternSearch] POST ${apiUrl} | prompt_preview="${prompt.substring(0, 80).replace(/\s+/g, ' ')}"`);
208
+ }
205
209
 
206
210
  const res = await fetch(apiUrl, {
207
211
  method: 'POST',
@@ -213,20 +217,23 @@ export async function searchPromptPattern(prompt: string, limit: number = 5): Pr
213
217
  });
214
218
 
215
219
  if (!res.ok) {
216
- if (debugPatternSearch) console.warn(`[PatternSearch] HTTP ${res.status} from ${apiUrl}`);
217
- return [];
218
- }
219
- const data = await res.json();
220
- const results = Array.isArray(data) ? data : (data.matches || data.results || []);
221
- if (debugPatternSearch) console.log(`[PatternSearch] ${results.length} patrones encontrados.`);
222
- return results;
223
- } catch (err: any) {
224
- if (process.env.DEBUG === 'true' || process.env.ARCALITY_DEBUG === 'true') {
225
- console.warn(`[PatternSearch] Error buscando patrones: ${err?.message || 'unknown'}`);
226
- }
227
- return [];
228
- }
229
- }
220
+ const responseText = await res.text().catch(() => '');
221
+ console.warn(`[PatternSearch] Fall� b�squeda | HTTP ${res.status} | endpoint=${apiUrl}${responseText ? ` | body=${responseText.substring(0, 250)}` : ''}`);
222
+ return [];
223
+ }
224
+ const data = await res.json();
225
+ const results = Array.isArray(data) ? data : (data.matches || data.results || []);
226
+ console.log(`[PatternSearch] Resultado de b�squeda | matches=${results.length}`);
227
+ if (debugPatternSearch && results.length > 0) {
228
+ const bestMatch = results[0] || {};
229
+ console.log(`[PatternSearch] Mejor match | id=${bestMatch.id || 'unknown'} | prompt="${String(bestMatch.prompt || bestMatch.original_prompt || bestMatch.name || '').substring(0, 120)}"`);
230
+ }
231
+ return results;
232
+ } catch (err: any) {
233
+ console.warn(`[PatternSearch] Error buscando patrones: ${err?.message || 'unknown'}`);
234
+ return [];
235
+ }
236
+ }
230
237
 
231
238
  export async function savePromptPattern(patternData: any): Promise<boolean> {
232
239
  if (!isConfigured()) return false;
@@ -239,7 +246,7 @@ export async function savePromptPattern(patternData: any): Promise<boolean> {
239
246
  mission_id: getMissionId(),
240
247
  ...patternData
241
248
  };
242
- if (process.env.DEBUG) console.log(`[PatternSave] 📤 POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
249
+ if (process.env.DEBUG) console.log(`[PatternSave] �x� POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
243
250
 
244
251
  const res = await fetch(apiUrl, {
245
252
  method: 'POST',
@@ -251,18 +258,18 @@ export async function savePromptPattern(patternData: any): Promise<boolean> {
251
258
  });
252
259
 
253
260
  if (!res.ok) {
254
- if (process.env.DEBUG) console.warn(`[PatternSave] ⚠️ HTTP ${res.status} al guardar patrón en ${apiUrl}: ${await res.text().catch(() => '')}`);
261
+ if (process.env.DEBUG) console.warn(`[PatternSave] �a�️ HTTP ${res.status} al guardar patrón en ${apiUrl}: ${await res.text().catch(() => '')}`);
255
262
  } else {
256
- if (process.env.DEBUG) console.log(`[PatternSave] Patrón guardado exitosamente.`);
263
+ if (process.env.DEBUG) console.log(`[PatternSave] �S& Patrón guardado exitosamente.`);
257
264
  }
258
265
  return res.ok;
259
266
  } catch (err: any) {
260
- if (process.env.DEBUG) console.warn(`[PatternSave] ⚠️ Error guardando patrón: ${err?.message || 'unknown'}`);
267
+ if (process.env.DEBUG) console.warn(`[PatternSave] �a�️ Error guardando patrón: ${err?.message || 'unknown'}`);
261
268
  return false;
262
269
  }
263
270
  }
264
271
 
265
- // ─── HELPER: Obtener campos ya conocidos para deduplicación ─────────────────
272
+ // ������ HELPER: Obtener campos ya conocidos para deduplicación ����������������������������������
266
273
 
267
274
  export async function getKnownFieldIdentifiers(pathUrl: string): Promise<string[]> {
268
275
  if (!isConfigured()) return [];
@@ -278,3 +285,4 @@ export async function getKnownFieldIdentifiers(pathUrl: string): Promise<string[
278
285
  return [];
279
286
  }
280
287
  }
288
+
@@ -81,7 +81,7 @@ function buildMissionVariant(routeEntry, variant, options = {}) {
81
81
  name: `${baseName}${variantCode}`,
82
82
  prompt,
83
83
  page: route,
84
- page_path: routeEntry.pagePath,
84
+ page_path: routeEntry.routeExamplePath || route,
85
85
  expected_result: expectedResult,
86
86
  tags: normalizeTags([...(routeEntry.tags || []), ...(variant.tags || []), ...((guidance.extraTags) || [])], promptFocus),
87
87
  priority: guidance.priorityOverride || variant.priority || routeEntry.priority || 'normal',
@@ -95,6 +95,7 @@ function buildMissionVariant(routeEntry, variant, options = {}) {
95
95
  context_status: guidance.contextStatus || { qaContext: 'missing', feasibility: 'missing' },
96
96
  route_example_path: routeEntry.routeExamplePath || route,
97
97
  route_params: routeEntry.routeParams || [],
98
+ source_file_path: routeEntry.pagePath,
98
99
  route_hash: simpleHash(`${route}|${routeEntry.pagePath}`),
99
100
  prompt_hash: simpleHash(prompt)
100
101
  }
@@ -221,23 +221,38 @@ function analyzeSourceSignals(sourceFile, route) {
221
221
  const fileLower = toPosix(sourceFile).toLowerCase();
222
222
  const semanticFileLower = fileLower.replace(/\([^/\\]+\)/g, '');
223
223
  const has = (...patterns) => patterns.some(pattern => lower.includes(pattern));
224
+ const count = (...patterns) => patterns.reduce((total, pattern) => total + (lower.match(new RegExp(pattern, 'g')) || []).length, 0);
224
225
  const routeHas = (...patterns) => patterns.some(pattern => routeLower.includes(pattern) || semanticFileLower.includes(pattern));
225
226
  const isRouterConfigFile = has('createbrowserrouter', 'useroutes', '<routes', '<route');
226
227
  const contextualHas = (...patterns) => {
227
228
  if (routeHas(...patterns)) return true;
228
229
  return !isRouterConfigFile && has(...patterns);
229
230
  };
231
+ const formControlCount = count(
232
+ '<input',
233
+ '<select',
234
+ '<textarea',
235
+ 'textfield',
236
+ 'inputbase',
237
+ 'autocomplete',
238
+ 'radiogroup',
239
+ 'checkbox'
240
+ );
241
+ const hasStepFlow = contextualHas('siguiente', 'continuar', 'anterior', 'atras', 'paso', 'stepper', 'wizard');
242
+ const hasValidationCopy = contextualHas('campo obligatorio', 'requerido', 'obligatorio', 'valida', 'validacion', 'validation');
230
243
 
231
244
  const signals = {
232
- hasForm: has('<form', 'useform', 'formik', 'react-hook-form', 'zodresolver', 'onsubmit', 'handleSubmit'.toLowerCase()),
245
+ hasForm: has('<form', 'useform', 'formik', 'react-hook-form', 'zodresolver', 'onsubmit', 'handlesubmit')
246
+ || formControlCount >= 2
247
+ || (formControlCount >= 1 && (hasStepFlow || hasValidationCopy)),
233
248
  hasTable: has('<table', 'datatable', 'ag-grid', 'tanstack table', 'role="table"', 'role="grid"'),
234
249
  hasSearch: contextualHas('search', 'buscar', 'query', 'placeholder="buscar', 'placeholder=\'buscar'),
235
250
  hasFilter: contextualHas('filter', 'filtro', 'filtrar'),
236
251
  hasCreateAction: contextualHas('crear', 'create', 'nuevo', 'new ', 'agregar', 'add '),
237
252
  hasEditAction: contextualHas('editar', 'edit', 'actualizar', 'update'),
238
- hasAuth: has('password', 'contraseña', 'iniciar sesión', 'login', 'sign in') || routeHas('login', 'signin', 'auth'),
253
+ hasAuth: has('password', 'contrase\u00f1a', 'iniciar sesi\u00f3n', 'login', 'sign in') || routeHas('login', 'signin', 'auth'),
239
254
  hasCheckout: contextualHas('payment', 'card', 'checkout', 'billing', 'stripe'),
240
- hasSettings: has('settings', 'configuración', 'preferencias', 'preferences') || routeHas('settings', 'config', 'preferences'),
255
+ hasSettings: has('settings', 'configuraci\u00f3n', 'preferencias', 'preferences') || routeHas('settings', 'config', 'preferences'),
241
256
  hasUpload: contextualHas('type="file"', "type='file'", 'dropzone', 'upload', 'subir archivo', 'subir imagen'),
242
257
  isDynamicRoute: /\[[^\]]+\]/.test(route) || /:\w+/.test(route),
243
258
  routeSegments: routeLower.replace(/^\/+/, '').split('/').filter(Boolean),
@@ -858,6 +858,25 @@ var assertUrlPatternTool = {
858
858
  required: ["pattern"]
859
859
  }
860
860
  };
861
+ var navigateToUrlTool = {
862
+ name: "navigate_to_url",
863
+ description: "QA Skill: Navigate directly to a relative route or absolute URL. Use this whenever the mission mentions a concrete route like '/counter' or when there is no visible menu/link for the target screen. Relative paths are resolved against the current page origin.",
864
+ input_schema: {
865
+ type: "object",
866
+ properties: {
867
+ target_url: {
868
+ type: "string",
869
+ description: "Relative route or absolute URL to open. Examples: '/counter', '/users/123', 'https://example.com/dashboard'."
870
+ },
871
+ wait_until: {
872
+ enum: ["domcontentloaded", "load", "networkidle"],
873
+ description: "Desired Playwright waitUntil strategy after navigation.",
874
+ default: "domcontentloaded"
875
+ }
876
+ },
877
+ required: ["target_url"]
878
+ }
879
+ };
861
880
  var measurePagePerformanceTool = {
862
881
  name: "measure_page_performance",
863
882
  description: "QA Skill: Measure page load time, network requests, and performance metrics (FCP, DOM loaded, total load). Use this to validate performance requirements or SLAs. Returns measured values and PASS/FAIL against optional thresholds.",
@@ -1058,6 +1077,7 @@ var qaAdvancedTools = [
1058
1077
  extractTableDataTool,
1059
1078
  captureConsoleErrorsTool,
1060
1079
  assertUrlPatternTool,
1080
+ navigateToUrlTool,
1061
1081
  measurePagePerformanceTool,
1062
1082
  saveNavigationCheckpointTool,
1063
1083
  restoreNavigationCheckpointTool,
@@ -1497,7 +1517,7 @@ async function pushRule(rule) {
1497
1517
  }
1498
1518
  const data = await res.json();
1499
1519
  if (process.env.DEBUG)
1500
- console.log(`\x1B[32m[CollectiveMemory] \u2705 Regla guardada: "${rule.title}"\x1B[0m`);
1520
+ console.log(`\x1B[32m[CollectiveMemory] \uFFFDS& Regla guardada: "${rule.title}"\x1B[0m`);
1501
1521
  return data.id ?? null;
1502
1522
  } catch (err) {
1503
1523
  if (process.env.DEBUG)
@@ -1564,7 +1584,7 @@ async function pushKnowledge(knowledge) {
1564
1584
  }
1565
1585
  const data = await res.json();
1566
1586
  if (process.env.DEBUG)
1567
- console.log(`\x1B[32m[CollectiveMemory] \u2705 Conocimiento guardado: "${knowledge.title}"\x1B[0m`);
1587
+ console.log(`\x1B[32m[CollectiveMemory] \uFFFDS& Conocimiento guardado: "${knowledge.title}"\x1B[0m`);
1568
1588
  return data.id ?? null;
1569
1589
  } catch (err) {
1570
1590
  if (process.env.DEBUG)
@@ -1573,8 +1593,10 @@ async function pushKnowledge(knowledge) {
1573
1593
  }
1574
1594
  }
1575
1595
  async function searchPromptPattern(prompt, limit = 5) {
1576
- if (!isConfigured())
1596
+ if (!isConfigured()) {
1597
+ console.warn(`[PatternSearch] Omitido: configuraci\uFFFDn incompleta para prompt-execution-patterns/search`);
1577
1598
  return [];
1599
+ }
1578
1600
  try {
1579
1601
  const apiUrl = process.env.ARCALITY_API_URL ? `${process.env.ARCALITY_API_URL}/api/v1/prompt-execution-patterns/search` : `${getBase()}/prompt-execution-patterns/search`;
1580
1602
  const payload = {
@@ -1584,8 +1606,9 @@ async function searchPromptPattern(prompt, limit = 5) {
1584
1606
  limit
1585
1607
  };
1586
1608
  const debugPatternSearch = process.env.DEBUG === "true" || process.env.ARCALITY_DEBUG === "true";
1609
+ console.log(`[PatternSearch] Iniciando b\uFFFDsqueda | prompt_chars=${prompt.length} | limit=${limit} | project_id=${getPid() || "missing"} | org_id=${getOrgId() || "missing"}`);
1587
1610
  if (debugPatternSearch) {
1588
- console.log(`[PatternSearch] POST ${apiUrl} | prompt_chars=${prompt.length} | limit=${limit}`);
1611
+ console.log(`[PatternSearch] POST ${apiUrl} | prompt_preview="${prompt.substring(0, 80).replace(/\s+/g, " ")}"`);
1589
1612
  }
1590
1613
  const res = await fetch(apiUrl, {
1591
1614
  method: "POST",
@@ -1596,19 +1619,20 @@ async function searchPromptPattern(prompt, limit = 5) {
1596
1619
  body: JSON.stringify(payload)
1597
1620
  });
1598
1621
  if (!res.ok) {
1599
- if (debugPatternSearch)
1600
- console.warn(`[PatternSearch] HTTP ${res.status} from ${apiUrl}`);
1622
+ const responseText = await res.text().catch(() => "");
1623
+ console.warn(`[PatternSearch] Fall\uFFFD b\uFFFDsqueda | HTTP ${res.status} | endpoint=${apiUrl}${responseText ? ` | body=${responseText.substring(0, 250)}` : ""}`);
1601
1624
  return [];
1602
1625
  }
1603
1626
  const data = await res.json();
1604
1627
  const results = Array.isArray(data) ? data : data.matches || data.results || [];
1605
- if (debugPatternSearch)
1606
- console.log(`[PatternSearch] ${results.length} patrones encontrados.`);
1628
+ console.log(`[PatternSearch] Resultado de b\uFFFDsqueda | matches=${results.length}`);
1629
+ if (debugPatternSearch && results.length > 0) {
1630
+ const bestMatch = results[0] || {};
1631
+ console.log(`[PatternSearch] Mejor match | id=${bestMatch.id || "unknown"} | prompt="${String(bestMatch.prompt || bestMatch.original_prompt || bestMatch.name || "").substring(0, 120)}"`);
1632
+ }
1607
1633
  return results;
1608
1634
  } catch (err) {
1609
- if (process.env.DEBUG === "true" || process.env.ARCALITY_DEBUG === "true") {
1610
- console.warn(`[PatternSearch] Error buscando patrones: ${err?.message || "unknown"}`);
1611
- }
1635
+ console.warn(`[PatternSearch] Error buscando patrones: ${err?.message || "unknown"}`);
1612
1636
  return [];
1613
1637
  }
1614
1638
  }
@@ -1624,7 +1648,7 @@ async function savePromptPattern(patternData) {
1624
1648
  ...patternData
1625
1649
  };
1626
1650
  if (process.env.DEBUG)
1627
- console.log(`[PatternSave] \u{1F4E4} POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
1651
+ console.log(`[PatternSave] \uFFFDx\uFFFD POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
1628
1652
  const res = await fetch(apiUrl, {
1629
1653
  method: "POST",
1630
1654
  headers: {
@@ -1635,15 +1659,15 @@ async function savePromptPattern(patternData) {
1635
1659
  });
1636
1660
  if (!res.ok) {
1637
1661
  if (process.env.DEBUG)
1638
- console.warn(`[PatternSave] \u26A0\uFE0F HTTP ${res.status} al guardar patr\xF3n en ${apiUrl}: ${await res.text().catch(() => "")}`);
1662
+ console.warn(`[PatternSave] \uFFFDa\uFFFD\uFE0F HTTP ${res.status} al guardar patr\xF3n en ${apiUrl}: ${await res.text().catch(() => "")}`);
1639
1663
  } else {
1640
1664
  if (process.env.DEBUG)
1641
- console.log(`[PatternSave] \u2705 Patr\xF3n guardado exitosamente.`);
1665
+ console.log(`[PatternSave] \uFFFDS& Patr\xF3n guardado exitosamente.`);
1642
1666
  }
1643
1667
  return res.ok;
1644
1668
  } catch (err) {
1645
1669
  if (process.env.DEBUG)
1646
- console.warn(`[PatternSave] \u26A0\uFE0F Error guardando patr\xF3n: ${err?.message || "unknown"}`);
1670
+ console.warn(`[PatternSave] \uFFFDa\uFFFD\uFE0F Error guardando patr\xF3n: ${err?.message || "unknown"}`);
1647
1671
  return false;
1648
1672
  }
1649
1673
  }
@@ -3064,6 +3088,7 @@ These are NOT optional. You MUST use these tools in these situations:
3064
3088
  | Before clicking SAVE/GUARDAR | \`validate_element_state\` | To confirm button is enabled |
3065
3089
  | After successful SAVE | \`extract_table_data\` | To verify record was created |
3066
3090
  | UI is frozen/unresponsive | \`capture_console_errors\` | To check for JS errors |
3091
+ | Mission mentions a concrete route like '/counter' and current URL is different, or the route has no visible menu item | \`navigate_to_url\` | IMMEDIATELY instead of improvising clicks |
3067
3092
  | Bug found in application | \`create_test_evidence\` | To document with annotated screenshot |
3068
3093
  | Mission complete | \`create_test_evidence\` | To document the final successful state |
3069
3094
  | Modal/popup blocking page | \`send_keyboard_event\` (Escape) | To close the overlay |
@@ -3085,6 +3110,7 @@ These are NOT optional. You MUST use these tools in these situations:
3085
3110
 
3086
3111
  3. **CURRENT STATE > MEMORY**: What you see in CURRENT COMPONENTS is the truth. SUCCESS MEMORY is a guide, not gospel.
3087
3112
  4. **PAGE TRANSITIONS**: If URL contains '/edit' or you see a 'Save' button, you ARE in the edit view. Don't go back.
3113
+ 5. **DIRECT ROUTE NAVIGATION (CRITICAL)**: If the mission mentions a concrete route such as '/counter', '/dashboard', '/users/123', or the reasoning says "navigate directly", you MUST use \`navigate_to_url\`. Do NOT simulate the browser address bar with clicks on logos, headers, or random navigation links.
3088
3114
  5. **MENUS & DROPDOWNS**:
3089
3115
  - After clicking a toggle/menu, WAIT for the next turn to see options. Never click the same menu icon twice in a row.
3090
3116
  - **DROPDOWN COMPLETION RULE (CRITICAL)**: After clicking an option in a dropdown ('[li]', '[option]'), IF the form requires you to fill MORE subsequent fields in the same section, STOP YOUR TURN AFTER SELECTING the option! Do NOT group the select action with filling the next fields. React/Next.js will re-render the DOM, detaching your cached 'idx' references causing 10000ms timeouts. Wait for the next turn to fill the next fields.
@@ -3510,6 +3536,19 @@ ${history.slice(-25).join("\n") || "None"}`
3510
3536
  const pass = should_match ? match : !match;
3511
3537
  const result = `URL Assertion: ${pass ? "PASS" : "FAIL"} (Current URL: ${url} | Pattern: ${pattern} | Expected match: ${should_match})`;
3512
3538
  toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: result });
3539
+ } else if (toolName === "navigate_to_url") {
3540
+ const { target_url, wait_until = "domcontentloaded" } = toolInput;
3541
+ console.log(`>>ARCALITY_STATUS>> ?? Navegando directamente a: ${target_url}...`);
3542
+ try {
3543
+ const baseUrl = this.page.url() || process.env.BASE_URL || "http://localhost:3000";
3544
+ const fullUrl = new URL(target_url, baseUrl).toString();
3545
+ await this.page.goto(fullUrl, { waitUntil: wait_until, timeout: 15e3 });
3546
+ await this.page.waitForLoadState("networkidle", { timeout: 3e3 }).catch(() => {
3547
+ });
3548
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `? Navigated to ${fullUrl}. Current URL: ${this.page.url()}` });
3549
+ } catch (e) {
3550
+ toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: `Error navigating to URL: ${e.message}` });
3551
+ }
3513
3552
  } else if (toolName === "measure_page_performance") {
3514
3553
  const { thresholds = {} } = toolInput;
3515
3554
  console.log(`>>ARCALITY_STATUS>> \u26A1 Midiendo rendimiento de p\xE1gina...`);
@@ -4234,6 +4273,23 @@ async function readHiddenPortalResult(page) {
4234
4273
  }
4235
4274
  return null;
4236
4275
  }
4276
+ function resolveTargetUrl(baseUrl, currentUrl, targetPath) {
4277
+ if (!targetPath)
4278
+ return currentUrl || baseUrl;
4279
+ if (targetPath.startsWith("http://") || targetPath.startsWith("https://"))
4280
+ return targetPath;
4281
+ const referenceUrl = currentUrl || baseUrl;
4282
+ const parsedBase = new URL(baseUrl || referenceUrl);
4283
+ const parsedReference = new URL(referenceUrl);
4284
+ const normalizedTarget = targetPath.trim();
4285
+ if (normalizedTarget.startsWith("/")) {
4286
+ const basePath = parsedBase.pathname.replace(/\/+$/, "");
4287
+ const targetAlreadyIncludesBase = basePath && (normalizedTarget === basePath || normalizedTarget.startsWith(`${basePath}/`));
4288
+ const combinedPath = targetAlreadyIncludesBase ? normalizedTarget : `${basePath}${normalizedTarget}`.replace(/\/{2,}/g, "/");
4289
+ return new URL(`${parsedBase.origin}${combinedPath}`).toString();
4290
+ }
4291
+ return new URL(normalizedTarget, parsedReference).toString();
4292
+ }
4237
4293
  function writeBatchMissionResult(payload) {
4238
4294
  const runDomainDir = process.env.ARCALITY_RUN_DOMAIN_DIR;
4239
4295
  if (!runDomainDir)
@@ -4616,16 +4672,18 @@ function writeBatchMissionResult(payload) {
4616
4672
  console.warn(` \u26A0\uFE0F Login autom\xE1tico omitido o ya autenticado.`);
4617
4673
  }
4618
4674
  if (target !== "/" && !page.url().includes(target)) {
4619
- const tgtUrl = target.startsWith("http") ? target : base + target;
4675
+ const tgtUrl = resolveTargetUrl(base, page.url(), target);
4620
4676
  if (!tgtUrl.startsWith("http"))
4621
4677
  throw new Error(`URL Inv\xE1lida: "${tgtUrl}"`);
4678
+ console.log(`>>ARCALITY_STATUS>> [Navigation] Navegando a ruta objetivo: ${tgtUrl}`);
4622
4679
  await page.goto(tgtUrl).catch(() => {
4623
4680
  });
4624
4681
  }
4625
4682
  } else {
4626
- const tgtUrl = target.startsWith("http") ? target : base + target;
4683
+ const tgtUrl = resolveTargetUrl(base, base, target);
4627
4684
  if (!tgtUrl.startsWith("http"))
4628
4685
  throw new Error(`URL Inv\xE1lida: "${tgtUrl}". Por favor proporciona una URL v\xE1lida incluyendo http:// o https://`);
4686
+ console.log(`>>ARCALITY_STATUS>> [Navigation] Abriendo ruta inicial: ${tgtUrl}`);
4629
4687
  await page.goto(tgtUrl);
4630
4688
  }
4631
4689
  await page.waitForLoadState("networkidle");