@arcadialdev/arcality 2.5.0 → 2.6.1

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/README.md CHANGED
@@ -29,16 +29,23 @@
29
29
  npm install @arcadialdev/arcality
30
30
  ```
31
31
 
32
- > After install, Arcality automatically adds the following scripts to your `package.json`:
33
- > ```json
34
- > "dev": "node scripts/gen-and-run.mjs",
35
- > "postinstall": "node scripts/postinstall.mjs",
36
- > "setup": "node scripts/setup.mjs",
37
- > "build:runner": "npx esbuild tests/_helpers/agentic-runner.spec.ts --bundle --platform=node --target=node18 --format=cjs --external:@playwright/test --external:chalk --external:dotenv --external:node-fetch --external:js-yaml --external:axios --external:crypto --external:fs --external:path --outfile=tests/_helpers/agentic-runner.bundle.spec.js",
38
- > "prepublishOnly": "npm run build:runner"
39
- > ```
40
- >
41
- > **Current version:** `2.5.0`
32
+
33
+ ### 2. Initialize your project
34
+ Run the setup wizard to connect your project and generate the configuration.
35
+
36
+ ```bash
37
+ npx arcality init
38
+ ```
39
+
40
+ ### 3. Run your first mission
41
+ ```bash
42
+ # Open interactive menu
43
+ npx arcality
44
+
45
+ # Or run directly (Ghost Mode)
46
+ npx arcality run
47
+ ```
48
+
42
49
 
43
50
  ### 📦 Updating Arcality
44
51
  Run the following to get the latest version:
@@ -47,12 +54,8 @@ npm update @arcadialdev/arcality
47
54
  ```
48
55
 
49
56
  ### ⚙️ Configuration (`arcality.config.json`)
50
- After initializing with `npm run arcality:init`, a config file is created in the project root. Edit it to set your project name, base URL, and authentication details. The CLI reads this file on every run, ensuring consistent environment variables.
57
+ After initializing with `npx arcality init`, a config file is created in the project root. Edit it to set your project name, base URL, and authentication details. The CLI reads this file on every run, ensuring consistent environment variables.
51
58
 
52
- ### 🚀 Getting Started (revised)
53
- 1. **Install** – `npm install @arcadialdev/arcality`
54
- 2. **Initialize** – `npm run arcality:init`
55
- 3. **Run a mission** – `npm run dev` (interactive menu) or `npm run dev -- --run "Your prompt"` for direct execution.
56
59
 
57
60
  ---
58
61
 
@@ -62,8 +65,11 @@ The Arcality flow is designed to be used globally or project-wide via `npx`. Bel
62
65
 
63
66
  | Command | Description | Internal Behavior |
64
67
  | :--- | :--- | :--- |
65
- | \`npx arcality run\` | **Mission Runner (Agent Mode)**<br/>Launches the QA Agent directly. | Skips the interactive menu, scans the _package.json_ ("Ghost Mode"), injects ephemeral _autoskills_, and unleashes the AI agent. Ideal for CI/CD. |
66
- | \`npx arcality --logs\` | **History Viewer**<br/>Displays test performance and tokens spent. | Reads the local \`arcality-history.log\` and renders a colorful table listing each mission, token consumption, and success status. |
68
+ | `npx arcality` | **Interactive Menu**<br/>Launches a visual menu in the terminal to guide the user. | Prompts for the Mission to execute. Ideal for new users who want a guided experience. |
69
+ | `npx arcality init` | **Initial Configuration**<br/>Connects the current project to Arcadial. | Prompts for your API Key and Project ID, generates `arcality.config.json`, and validates the portal connection. |
70
+ | `npx arcality run` | **Mission Runner (Agent Mode)**<br/>Launches the QA Agent directly. | Skips the interactive menu, scans the _package.json_ ("Ghost Mode"), and unleashes the AI agent. Ideal for CI/CD. |
71
+ | `npx arcality --logs` | **History Viewer**<br/>Displays test performance and tokens spent. | Reads the local `arcality-history.log` and renders a colorful table listing each mission and success status. |
72
+
67
73
 
68
74
  ---
69
75
 
@@ -81,7 +87,7 @@ Arcality operates on a highly decoupled modular system designed for maximum resi
81
87
 
82
88
  ## 📁 Configuration (`arcality.config.json`)
83
89
 
84
- When you run `arcality init`, a configuration is generated in your project root.
90
+ When you run `npx arcality init`, a configuration is generated in your project root.
85
91
  ```json
86
92
  {
87
93
  "project": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadialdev/arcality",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
4
4
  "description": "El primer ingeniero QA Autónomo integrado al CI/CD. Creado por Arcadial.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -770,12 +770,8 @@ async function main() {
770
770
  }
771
771
  } catch { /* silencioso */ }
772
772
 
773
- // 2. Llamar endMission con estado 'canceled'
774
- try {
775
- const { endMission: endM } = await import('../src/arcalityClient.mjs');
776
- await endM(mission.mission_id, 'canceled', null, 'user_canceled');
777
- console.log(chalk.gray(' >> Misión registrada como "cancelada" en el servidor.'));
778
- } catch { /* silencioso si el backend no está disponible */ }
773
+ // 2. Registraremos la misión como 'canceled' en el bloque finally
774
+ console.log(chalk.gray(' >> Registrando misión como "cancelada"...'));
779
775
 
780
776
  // 3. Matar el proceso hijo de Playwright — hacerlo AL FINAL para dar tiempo
781
777
  // a que el agente-log y endMission terminen antes del kill.
@@ -852,6 +848,11 @@ async function main() {
852
848
  process.exit(1);
853
849
  }
854
850
 
851
+ let missionResult = null;
852
+ let finalFailReason = null;
853
+ let finalUsageData = undefined;
854
+ let finalReportUrl = null;
855
+
855
856
  try {
856
857
  // No test file path is passed — playwright.config.js uses testMatch:'agentic-runner.spec.ts'
857
858
  // This eliminates the Windows regex path issue permanently on any user's machine.
@@ -872,46 +873,15 @@ async function main() {
872
873
  process.off('SIGINT', cancelHandler);
873
874
  process.off('SIGTERM', cancelHandler);
874
875
 
875
- if (userCanceledMission) return; // El handler ya tomó el control
876
- if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Mission completed successfully.'));
877
- else console.log(chalk.green('✅ Mission completed successfully.'));
878
-
879
- let finalUsage = undefined;
880
- try {
881
- const ctxDir = process.env.CONTEXT_DIR;
882
- if (fs.existsSync(ctxDir)) {
883
- const files = fs.readdirSync(ctxDir);
884
- const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
885
- if (logFiles.length > 0) {
886
- const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
887
- const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
888
- finalUsage = logData.usage;
889
- }
890
- }
891
- } catch(e) {}
892
-
893
- // ── Upload Evidence ──
894
- const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
895
- const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
896
-
897
- try {
898
- const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
899
- if (sasData && sasData.sasUrl) {
900
- if (!process.argv.includes('--debug')) s.start('☁️ Uploading evidence to Azure...');
901
- const reportsDir = process.env.REPORTS_DIR || path.join(ORIGINAL_CWD, 'tests-report');
902
-
903
- await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
904
-
905
- await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
906
- if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
907
- }
908
- } catch (e) {
909
- if (process.argv.includes('--debug')) console.log(chalk.yellow(`⚠️ Could not upload evidence: ${e.message}`));
910
- if (!process.argv.includes('--debug')) s.stop(chalk.yellow('⚠️ Evidence upload failed.'));
876
+ if (userCanceledMission) {
877
+ missionResult = 'canceled';
878
+ finalFailReason = 'user_canceled';
879
+ return; // El handler ya tomó el control, finally se ejecutará y luego saldrá del CLI
911
880
  }
912
881
 
913
- // ── End Mission ──
914
- await endMission(mission.mission_id, 'success', finalUsage);
882
+ missionResult = 'success';
883
+ if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Mission completed successfully.'));
884
+ else console.log(chalk.green('✅ Mission completed successfully.'));
915
885
 
916
886
  // ── Ping Project ──
917
887
  await pingProject(finalProjectId, process.env.ARCALITY_API_KEY);
@@ -967,58 +937,24 @@ async function main() {
967
937
  process.off('SIGTERM', cancelHandler);
968
938
 
969
939
  if (userCanceledMission) {
940
+ missionResult = 'canceled';
941
+ finalFailReason = 'user_canceled';
970
942
  // El cancelHandler ya registró la cancelación en el servidor.
971
943
  // Solo mostramos un mensaje y dejamos que el finally genere el reporte.
972
944
  console.log(chalk.yellow('\n⚠️ Misión cancelada — generando reporte de evidencia...'));
973
945
  } else {
946
+ missionResult = 'failed';
947
+ finalFailReason = 'timeout_or_crash';
974
948
  s.stop(chalk.red(`❌ Mission could not be completed.`));
975
949
  console.error(chalk.red(`\nError Details: ${e.message}`));
976
950
  if (e.stack && process.argv.includes('--debug')) {
977
951
  console.error(chalk.gray(e.stack));
978
952
  }
979
-
980
- // Si NO fue cancelación de usuario, cerrar la misión como fallida en el servidor
981
- try {
982
- let finalUsage = undefined;
983
- let failReason = "timeout_or_crash";
984
- try {
985
- const ctxDir = process.env.CONTEXT_DIR;
986
- if (fs.existsSync(ctxDir)) {
987
- const files = fs.readdirSync(ctxDir);
988
- const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
989
- if (logFiles.length > 0) {
990
- const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
991
- const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
992
- finalUsage = logData.usage;
993
- failReason = logData.fail_reason || failReason;
994
- }
995
- }
996
- } catch(e) {}
997
-
998
- // ── Upload Evidence ──
999
- const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
1000
- const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
1001
-
1002
- try {
1003
- const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
1004
- if (sasData && sasData.sasUrl) {
1005
- if (!process.argv.includes('--debug')) s.start('☁️ Uploading evidence to Azure...');
1006
- const reportsDir = process.env.REPORTS_DIR || path.join(ORIGINAL_CWD, 'tests-report');
1007
-
1008
- await run("node", [path.join(PROJECT_ROOT, "scripts/rebrand-report.mjs")]).catch(() => { });
1009
-
1010
- await uploadEvidence(reportsDir, sasData.sasUrl, sasData.basePath);
1011
- if (!process.argv.includes('--debug')) s.stop(chalk.green('✅ Evidence uploaded successfully.'));
1012
- }
1013
- } catch (e) {}
1014
-
1015
- await endMission(mission.mission_id, 'failed', finalUsage, failReason);
1016
- } catch { }
1017
953
  }
1018
954
  } finally {
1019
- // Si fue cancelación de usuario, dar un breve margen para que el cancelHandler
1020
- // async (endMission, escritura de log) termine antes de generar el reporte.
1021
955
  if (userCanceledMission) {
956
+ missionResult = 'canceled';
957
+ finalFailReason = 'user_canceled';
1022
958
  await new Promise(r => setTimeout(r, 800));
1023
959
  }
1024
960
 
@@ -1029,6 +965,58 @@ async function main() {
1029
965
  const reportDir = process.env.REPORTS_DIR || 'tests-report';
1030
966
  const arcalityPath = path.resolve(reportDir, 'index.html');
1031
967
 
968
+ sRep.stop();
969
+
970
+ // Read final usage and failReason from agent-log
971
+ try {
972
+ const ctxDir = process.env.CONTEXT_DIR;
973
+ if (fs.existsSync(ctxDir)) {
974
+ const files = fs.readdirSync(ctxDir);
975
+ const logFiles = files.filter(f => f.startsWith('agent-log-') && f.endsWith('.json')).sort();
976
+ if (logFiles.length > 0) {
977
+ const lastLog = path.join(ctxDir, logFiles[logFiles.length - 1]);
978
+ const logData = JSON.parse(fs.readFileSync(lastLog, 'utf8'));
979
+ finalUsageData = logData.usage || finalUsageData;
980
+ if (missionResult === 'failed' && !finalFailReason) {
981
+ finalFailReason = logData.fail_reason || "timeout_or_crash";
982
+ }
983
+ }
984
+ }
985
+ } catch(e) {}
986
+
987
+ // ── Upload Evidence ──
988
+ try {
989
+ const { endMission, getEvidenceSasToken } = await import('../src/arcalityClient.mjs');
990
+ const { uploadEvidence } = await import('../src/EvidenceUploader.mjs');
991
+
992
+ const sasData = await getEvidenceSasToken(mission.mission_id, finalProjectId);
993
+ if (process.argv.includes('--debug')) console.log(`[DEBUG] sasData received:`, sasData);
994
+
995
+ // Soporte para camelCase y snake_case debido al serializador JSON del backend
996
+ const actualSasUrl = sasData?.sasTokenUrl || sasData?.sasUrl || sasData?.sas_url;
997
+ const actualBasePath = sasData?.basePath || sasData?.base_path;
998
+
999
+ if (sasData && actualSasUrl && actualBasePath) {
1000
+ if (!process.argv.includes('--debug')) sRep.start('☁️ Uploading evidence to Azure...');
1001
+
1002
+ await uploadEvidence(reportDir, actualSasUrl, actualBasePath);
1003
+
1004
+ // Construct the public URL
1005
+ try {
1006
+ const parsedSas = new URL(actualSasUrl);
1007
+ finalReportUrl = `${parsedSas.origin}${parsedSas.pathname}/${actualBasePath}/index.html`;
1008
+ } catch(e) {}
1009
+
1010
+ if (!process.argv.includes('--debug')) sRep.stop(chalk.green('✅ Evidence uploaded successfully.'));
1011
+ }
1012
+
1013
+ // ── End Mission ──
1014
+ await endMission(mission.mission_id, missionResult || 'failed', finalUsageData, finalFailReason, finalReportUrl);
1015
+ } catch (e) {
1016
+ if (process.argv.includes('--debug')) console.log(chalk.yellow(`⚠️ Could not upload evidence or end mission: ${e.message}`));
1017
+ if (!process.argv.includes('--debug')) sRep.stop(chalk.yellow('⚠️ Evidence upload or end mission failed.'));
1018
+ }
1019
+
1032
1020
  if (globalReportProcess) {
1033
1021
  try {
1034
1022
  if (process.platform === "win32") {
@@ -1052,7 +1040,6 @@ async function main() {
1052
1040
  }
1053
1041
 
1054
1042
  await new Promise(resolve => setTimeout(resolve, 2000));
1055
- sRep.stop(chalk.cyan('Report process initialized.'));
1056
1043
 
1057
1044
  // Ping project after report
1058
1045
  await pingProject(
@@ -1,45 +1,45 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { ContainerClient } from '@azure/storage-blob';
4
- import mime from 'mime-types';
5
-
6
- /**
7
- * Subes la evidencia de prueba iterativamente a Azure Blob Storage usando una URL de SAS.
8
- * @param {string} reportsDir Directorio local de reportes.
9
- * @param {string} sasUrl URL de SAS para el ContainerClient.
10
- * @param {string} basePath Path base dentro del contenedor de Azure.
11
- */
12
- export async function uploadEvidence(reportsDir, sasUrl, basePath) {
13
- if (!fs.existsSync(reportsDir)) return;
14
-
15
- const containerClient = new ContainerClient(sasUrl);
16
-
17
- async function walkAndUpload(currentDir, relativePath = '') {
18
- const entries = fs.readdirSync(currentDir, { withFileTypes: true });
19
- const uploadPromises = [];
20
-
21
- for (const entry of entries) {
22
- const fullPath = path.join(currentDir, entry.name);
23
- const entryRelativePath = path.posix.join(relativePath, entry.name);
24
-
25
- if (entry.isDirectory()) {
26
- uploadPromises.push(walkAndUpload(fullPath, entryRelativePath));
27
- } else {
28
- // Blob path en Azure: basePath/ruta_relativa_del_archivo
29
- const blobPath = `${basePath}/${entryRelativePath}`;
30
- const blockBlobClient = containerClient.getBlockBlobClient(blobPath);
31
-
32
- const contentType = mime.lookup(entry.name) || 'application/octet-stream';
33
-
34
- uploadPromises.push(
35
- blockBlobClient.uploadFile(fullPath, {
36
- blobHTTPHeaders: { blobContentType: contentType }
37
- })
38
- );
39
- }
40
- }
41
- await Promise.all(uploadPromises);
42
- }
43
-
44
- await walkAndUpload(reportsDir);
45
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { ContainerClient } from '@azure/storage-blob';
4
+ import mime from 'mime-types';
5
+
6
+ /**
7
+ * Subes la evidencia de prueba iterativamente a Azure Blob Storage usando una URL de SAS.
8
+ * @param {string} reportsDir Directorio local de reportes.
9
+ * @param {string} sasUrl URL de SAS para el ContainerClient.
10
+ * @param {string} basePath Path base dentro del contenedor de Azure.
11
+ */
12
+ export async function uploadEvidence(reportsDir, sasUrl, basePath) {
13
+ if (!fs.existsSync(reportsDir)) return;
14
+
15
+ const containerClient = new ContainerClient(sasUrl);
16
+
17
+ async function walkAndUpload(currentDir, relativePath = '') {
18
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
19
+ const uploadPromises = [];
20
+
21
+ for (const entry of entries) {
22
+ const fullPath = path.join(currentDir, entry.name);
23
+ const entryRelativePath = path.posix.join(relativePath, entry.name);
24
+
25
+ if (entry.isDirectory()) {
26
+ uploadPromises.push(walkAndUpload(fullPath, entryRelativePath));
27
+ } else {
28
+ // Blob path en Azure: basePath/ruta_relativa_del_archivo
29
+ const blobPath = `${basePath}/${entryRelativePath}`;
30
+ const blockBlobClient = containerClient.getBlockBlobClient(blobPath);
31
+
32
+ const contentType = mime.lookup(entry.name) || 'application/octet-stream';
33
+
34
+ uploadPromises.push(
35
+ blockBlobClient.uploadFile(fullPath, {
36
+ blobHTTPHeaders: { blobContentType: contentType }
37
+ })
38
+ );
39
+ }
40
+ }
41
+ await Promise.all(uploadPromises);
42
+ }
43
+
44
+ await walkAndUpload(reportsDir);
45
+ }
@@ -144,7 +144,7 @@ export class KnowledgeService {
144
144
  if (data.status === 'skipped_duplicate_dom') {
145
145
  // console.log(chalk.gray(`[Knowledge] Ingesta omitida (Duplicado).`));
146
146
  } else {
147
- console.log(chalk.cyan(`[Knowledge] Ingesta exitosa (Project: ${payload.project_id})`));
147
+ if (process.env.DEBUG) console.log(chalk.cyan(`[Knowledge] Ingesta exitosa (Project: ${payload.project_id})`));
148
148
  }
149
149
  }
150
150
  } catch (e: any) {
@@ -250,7 +250,7 @@ export async function startMission(prompt, targetUrl) {
250
250
  * @param {'success'|'failed'|'cancelled'} result
251
251
  * @param {object} usage - Optional usage statistics directly from the AI
252
252
  */
253
- export async function endMission(missionId, result, usage = null, failReason = null) {
253
+ export async function endMission(missionId, result, usage = null, failReason = null, reportUrl = null) {
254
254
  const apiBase = getEffectiveApiBase();
255
255
  if (!apiBase || !missionId) return { ok: true };
256
256
 
@@ -262,7 +262,7 @@ export async function endMission(missionId, result, usage = null, failReason = n
262
262
  'Content-Type': 'application/json',
263
263
  'x-api-key': key
264
264
  },
265
- body: JSON.stringify({ result, usage, failReason })
265
+ body: JSON.stringify({ result, usage, failReason, reportUrl })
266
266
  });
267
267
  } catch { }
268
268
  return { ok: true };
@@ -288,25 +288,32 @@ function simpleHash(str) {
288
288
  */
289
289
  export async function getEvidenceSasToken(missionId, projectId) {
290
290
  const apiBase = getEffectiveApiBase();
291
- if (!apiBase || !missionId || !projectId) return null;
291
+ if (!apiBase || !missionId) return null;
292
292
 
293
293
  const key = getEffectiveApiKey();
294
294
  if (!key) return null;
295
295
 
296
296
  try {
297
+ const organization_id = process.env.ARCALITY_ORG_ID || '';
297
298
  const res = await fetch(`${apiBase}/api/v1/missions/${missionId}/evidence/sas`, {
298
299
  method: 'POST',
299
300
  headers: {
300
301
  'Content-Type': 'application/json',
301
- 'Authorization': `Bearer ${key}`
302
+ 'x-api-key': key
302
303
  },
303
- body: JSON.stringify({ projectId })
304
+ body: JSON.stringify({ organization_id, project_id: projectId })
304
305
  });
305
306
 
306
- if (!res.ok) return null;
307
+ if (!res.ok) {
308
+ const txt = await res.text();
309
+ console.log(`[DEBUG] SAS Token API Failed: ${res.status} - ${txt}`);
310
+ return null;
311
+ }
307
312
 
308
- return await res.json();
309
- } catch {
313
+ const data = await res.json();
314
+ return data;
315
+ } catch (e) {
316
+ console.log(`[DEBUG] SAS Token API Error: ${e.message}`);
310
317
  return null;
311
318
  }
312
319
  }
@@ -73,15 +73,15 @@ export async function pushRule(rule: {
73
73
  });
74
74
 
75
75
  if (!res.ok) {
76
- console.warn(`[CollectiveMemory] pushRule HTTP ${res.status}: ${await res.text().catch(() => '')}`);
76
+ if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushRule HTTP ${res.status}: ${await res.text().catch(() => '')}`);
77
77
  return null;
78
78
  }
79
79
 
80
80
  const data = await res.json();
81
- console.log(`\x1b[32m[CollectiveMemory] ✅ Regla guardada: "${rule.title}"\x1b[0m`);
81
+ if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] ✅ Regla guardada: "${rule.title}"\x1b[0m`);
82
82
  return data.id ?? null;
83
83
  } catch (err: any) {
84
- console.warn(`[CollectiveMemory] pushRule error: ${err?.message}`);
84
+ if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushRule error: ${err?.message}`);
85
85
  return null;
86
86
  }
87
87
  }
@@ -164,15 +164,15 @@ export async function pushKnowledge(knowledge: {
164
164
  });
165
165
 
166
166
  if (!res.ok) {
167
- console.warn(`[CollectiveMemory] pushKnowledge HTTP ${res.status}: ${await res.text().catch(() => '')}`);
167
+ if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushKnowledge HTTP ${res.status}: ${await res.text().catch(() => '')}`);
168
168
  return null;
169
169
  }
170
170
 
171
171
  const data = await res.json();
172
- console.log(`\x1b[32m[CollectiveMemory] ✅ Conocimiento guardado: "${knowledge.title}"\x1b[0m`);
172
+ if (process.env.DEBUG) console.log(`\x1b[32m[CollectiveMemory] ✅ Conocimiento guardado: "${knowledge.title}"\x1b[0m`);
173
173
  return data.id ?? null;
174
174
  } catch (err: any) {
175
- console.warn(`[CollectiveMemory] pushKnowledge error: ${err?.message}`);
175
+ if (process.env.DEBUG) console.warn(`[CollectiveMemory] pushKnowledge error: ${err?.message}`);
176
176
  return null;
177
177
  }
178
178
  }
@@ -228,7 +228,7 @@ export async function savePromptPattern(patternData: any): Promise<boolean> {
228
228
  mission_id: getMissionId(),
229
229
  ...patternData
230
230
  };
231
- console.log(`[PatternSave] 📤 POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
231
+ if (process.env.DEBUG) console.log(`[PatternSave] 📤 POST ${apiUrl} | org=${payload.organization_id} proj=${payload.project_id} mission=${payload.mission_id}`);
232
232
 
233
233
  const res = await fetch(apiUrl, {
234
234
  method: 'POST',
@@ -240,13 +240,13 @@ export async function savePromptPattern(patternData: any): Promise<boolean> {
240
240
  });
241
241
 
242
242
  if (!res.ok) {
243
- console.warn(`[PatternSave] ⚠️ HTTP ${res.status} al guardar patrón en ${apiUrl}: ${await res.text().catch(() => '')}`);
243
+ if (process.env.DEBUG) console.warn(`[PatternSave] ⚠️ HTTP ${res.status} al guardar patrón en ${apiUrl}: ${await res.text().catch(() => '')}`);
244
244
  } else {
245
- console.log(`[PatternSave] ✅ Patrón guardado exitosamente.`);
245
+ if (process.env.DEBUG) console.log(`[PatternSave] ✅ Patrón guardado exitosamente.`);
246
246
  }
247
247
  return res.ok;
248
248
  } catch (err: any) {
249
- console.warn(`[PatternSave] ⚠️ Error guardando patrón: ${err?.message || 'unknown'}`);
249
+ if (process.env.DEBUG) console.warn(`[PatternSave] ⚠️ Error guardando patrón: ${err?.message || 'unknown'}`);
250
250
  return false;
251
251
  }
252
252
  }
@@ -23,7 +23,7 @@ export class SecurityScanner {
23
23
  * @returns A promise that resolves to an array of found vulnerabilities.
24
24
  */
25
25
  async runAllScans(): Promise<SecurityFinding[]> {
26
- console.log('[QA-SEC] Starting comprehensive security scan...');
26
+ if (process.env.DEBUG) console.log('[QA-SEC] Starting comprehensive security scan...');
27
27
 
28
28
  // Scan 1: Audit HTTP Security Headers
29
29
  const headerVulnerabilities = await audit_http_headers(this.page);
@@ -38,7 +38,7 @@ export class SecurityScanner {
38
38
 
39
39
  // More scans like auth bypass for known admin URLs could be added here.
40
40
 
41
- console.log(`[QA-SEC] Comprehensive scan finished. Found ${this.report.length} potential vulnerabilities.`);
41
+ if (process.env.DEBUG) console.log(`[QA-SEC] Comprehensive scan finished. Found ${this.report.length} potential vulnerabilities.`);
42
42
  return this.getReport();
43
43
  }
44
44
 
@@ -55,7 +55,7 @@ export class SecurityScanner {
55
55
  const payload = 'https://www.evil-redirect.com';
56
56
  url.searchParams.set(param, payload);
57
57
 
58
- console.log(`[QA-SEC] Testing Open Redirect on param: ${param}`);
58
+ if (process.env.DEBUG) console.log(`[QA-SEC] Testing Open Redirect on param: ${param}`);
59
59
 
60
60
  try {
61
61
  await this.page.goto(url.toString(), { waitUntil: 'networkidle' });
@@ -89,7 +89,7 @@ export class SecurityScanner {
89
89
  await this.page.goBack();
90
90
  }
91
91
  } catch (error) {
92
- console.log(`[QA-SEC] Error during open redirect test, this might be expected if the navigation was blocked. Continuing scan.`);
92
+ if (process.env.DEBUG) console.log(`[QA-SEC] Error during open redirect test, this might be expected if the navigation was blocked. Continuing scan.`);
93
93
  // It's possible the navigation to the evil site fails, which is good.
94
94
  // We should ensure we can recover and continue.
95
95
  await this.page.goBack(); // Try to recover state