@arcadialdev/arcality 4.1.0 → 4.1.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/.agents/skills/db-validation-evidence/SKILL.md +43 -0
- package/.agents/skills/db-validation-evidence/agents/openai.yaml +4 -0
- package/.agents/skills/db-validation-evidence/references/mission-patterns.md +130 -0
- package/.agents/skills/evidence-synthesis-reporting/SKILL.md +31 -0
- package/.agents/skills/form-expert/SKILL.md +98 -0
- package/.agents/skills/investigation-protocol/SKILL.md +56 -0
- package/.agents/skills/mission-generation-reviewer/SKILL.md +25 -0
- package/.agents/skills/modal-master/SKILL.md +46 -0
- package/.agents/skills/native-control-expert/SKILL.md +74 -0
- package/.agents/skills/qa-context-governance/SKILL.md +23 -0
- package/.agents/skills/security-evidence-triage/SKILL.md +23 -0
- package/.agents/skills/test-data-lifecycle/SKILL.md +24 -0
- package/README.md +103 -163
- package/bin/arcality.mjs +25 -25
- package/package.json +75 -75
- package/scripts/edit-config.mjs +843 -0
- package/scripts/gen-and-run.mjs +237 -169
- package/scripts/generate.mjs +236 -236
- package/scripts/init.mjs +47 -47
- package/src/configManager.mjs +13 -13
- package/src/envSetup.ts +229 -205
- package/src/services/codebaseAnalyzer.mjs +59 -59
- package/src/services/databaseValidationService.mjs +598 -0
- package/src/services/executionEvidenceService.mjs +124 -0
- package/src/services/generatedMissionSchema.mjs +76 -76
- package/src/services/generatedMissionStore.mjs +117 -117
- package/src/services/generationContext.mjs +242 -242
- package/src/services/mcpStdioClient.mjs +204 -0
- package/src/services/missionGenerator.mjs +329 -329
- package/src/services/routeDiscovery.mjs +762 -762
- package/tests/_helpers/ArcalityReporter.js +1342 -1255
- package/tests/_helpers/agentic-runner.bundle.spec.js +1354 -243
- package/.agent/skills/form-expert.md +0 -102
- package/.agent/skills/investigation-protocol.md +0 -61
- package/.agent/skills/modal-master.md +0 -41
- package/.agent/skills/native-control-expert.md +0 -82
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import {
|
|
9
|
+
intro,
|
|
10
|
+
outro,
|
|
11
|
+
text,
|
|
12
|
+
password as passwordPrompt,
|
|
13
|
+
select,
|
|
14
|
+
note,
|
|
15
|
+
isCancel,
|
|
16
|
+
cancel,
|
|
17
|
+
confirm
|
|
18
|
+
} from '@clack/prompts';
|
|
19
|
+
import chalk from 'chalk';
|
|
20
|
+
import {
|
|
21
|
+
configExists,
|
|
22
|
+
loadProjectConfig,
|
|
23
|
+
saveProjectConfig
|
|
24
|
+
} from '../src/configManager.mjs';
|
|
25
|
+
import {
|
|
26
|
+
loadDatabaseMcpConfig,
|
|
27
|
+
resolveDatabaseConnectionEnv,
|
|
28
|
+
resolveDatabaseProvider
|
|
29
|
+
} from '../src/services/databaseValidationService.mjs';
|
|
30
|
+
import { callMcpToolWithConfig, extractRowsFromMcpToolResult } from '../src/services/mcpStdioClient.mjs';
|
|
31
|
+
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = path.dirname(__filename);
|
|
34
|
+
const PACKAGE_ROOT = process.env.ARCALITY_ROOT || path.resolve(__dirname, '..');
|
|
35
|
+
const DEFAULT_DB_CATALOG_PATH = '.arcality/db-validations.yaml';
|
|
36
|
+
const DEFAULT_MCP_CONFIG_PATH = '.arcality/mcp.postgres.json';
|
|
37
|
+
const DEFAULT_MCP_TEMPLATE_PATH = path.join(PACKAGE_ROOT, '.arcality', 'mcp.postgres.example.json');
|
|
38
|
+
|
|
39
|
+
function deepClone(value) {
|
|
40
|
+
return JSON.parse(JSON.stringify(value));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isValidUrl(url) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function detectFramework(projectRoot) {
|
|
53
|
+
try {
|
|
54
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
55
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
56
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
57
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
58
|
+
if (deps.next) return 'Next.js';
|
|
59
|
+
if (deps.nuxt || deps.nuxt3) return 'Nuxt';
|
|
60
|
+
if (deps.vite) return 'Vite';
|
|
61
|
+
if (deps['react-scripts']) return 'Create React App';
|
|
62
|
+
if (deps['@angular/core']) return 'Angular';
|
|
63
|
+
if (deps.vue) return 'Vue.js';
|
|
64
|
+
if (deps.svelte || deps['@sveltejs/kit']) return 'Svelte';
|
|
65
|
+
return null;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureArcalityDir(projectRoot) {
|
|
72
|
+
const dir = path.join(projectRoot, '.arcality');
|
|
73
|
+
if (!fs.existsSync(dir)) {
|
|
74
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
return dir;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function toEnvKey(value) {
|
|
80
|
+
return String(value || '')
|
|
81
|
+
.trim()
|
|
82
|
+
.toUpperCase()
|
|
83
|
+
.replace(/[^A-Z0-9]+/g, '_')
|
|
84
|
+
.replace(/^_+|_+$/g, '');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseEnvFile(content) {
|
|
88
|
+
const entries = new Map();
|
|
89
|
+
const lines = content.split(/\r?\n/);
|
|
90
|
+
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
94
|
+
const eqIndex = line.indexOf('=');
|
|
95
|
+
if (eqIndex === -1) continue;
|
|
96
|
+
const key = line.slice(0, eqIndex).trim();
|
|
97
|
+
let value = line.slice(eqIndex + 1).trim();
|
|
98
|
+
if (
|
|
99
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
100
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
101
|
+
) {
|
|
102
|
+
value = value.slice(1, -1);
|
|
103
|
+
}
|
|
104
|
+
entries.set(key, value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { entries, lines };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function escapeEnvValue(value) {
|
|
111
|
+
const stringValue = String(value ?? '');
|
|
112
|
+
const needsQuotes = /[\s"'`\\]/.test(stringValue);
|
|
113
|
+
const escaped = stringValue.replace(/"/g, '\\"');
|
|
114
|
+
return needsQuotes ? `"${escaped}"` : escaped;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readEnvMap(projectRoot) {
|
|
118
|
+
const envPath = path.join(projectRoot, '.env');
|
|
119
|
+
if (!fs.existsSync(envPath)) {
|
|
120
|
+
return { path: envPath, entries: new Map(), rawLines: [] };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const raw = fs.readFileSync(envPath, 'utf8');
|
|
124
|
+
const parsed = parseEnvFile(raw);
|
|
125
|
+
return { path: envPath, entries: parsed.entries, rawLines: parsed.lines };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function syncEnvFromLocalFile(projectRoot) {
|
|
129
|
+
const envState = readEnvMap(projectRoot);
|
|
130
|
+
for (const [key, value] of envState.entries.entries()) {
|
|
131
|
+
process.env[key] = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writeEnvUpdates(projectRoot, updates) {
|
|
136
|
+
const envState = readEnvMap(projectRoot);
|
|
137
|
+
const lineIndexByKey = new Map();
|
|
138
|
+
|
|
139
|
+
envState.rawLines.forEach((line, index) => {
|
|
140
|
+
const eqIndex = line.indexOf('=');
|
|
141
|
+
if (eqIndex === -1) return;
|
|
142
|
+
const key = line.slice(0, eqIndex).trim();
|
|
143
|
+
if (key && !key.startsWith('#')) {
|
|
144
|
+
lineIndexByKey.set(key, index);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const lines = envState.rawLines.length > 0 ? [...envState.rawLines] : ['# Variables locales de Arcality', ''];
|
|
149
|
+
|
|
150
|
+
for (const [key, rawValue] of Object.entries(updates)) {
|
|
151
|
+
if (rawValue === undefined) continue;
|
|
152
|
+
const rendered = `${key}=${escapeEnvValue(rawValue)}`;
|
|
153
|
+
if (lineIndexByKey.has(key)) {
|
|
154
|
+
lines[lineIndexByKey.get(key)] = rendered;
|
|
155
|
+
} else {
|
|
156
|
+
if (lines.length > 0 && lines[lines.length - 1] !== '') {
|
|
157
|
+
lines.push('');
|
|
158
|
+
}
|
|
159
|
+
lines.push(rendered);
|
|
160
|
+
lineIndexByKey.set(key, lines.length - 1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (lines[lines.length - 1] !== '') {
|
|
165
|
+
lines.push('');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fs.writeFileSync(envState.path, lines.join('\n'), 'utf8');
|
|
169
|
+
for (const [key, rawValue] of Object.entries(updates)) {
|
|
170
|
+
if (rawValue !== undefined) process.env[key] = String(rawValue);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
function getDbState(projectRoot) {
|
|
176
|
+
const envState = readEnvMap(projectRoot);
|
|
177
|
+
const provider = String(envState.entries.get('ARCALITY_DB_PROVIDER') || process.env.ARCALITY_DB_PROVIDER || '').trim().toLowerCase();
|
|
178
|
+
const profile = String(envState.entries.get('ARCALITY_DB_PROFILE') || process.env.ARCALITY_DB_PROFILE || 'qa_local').trim();
|
|
179
|
+
const profileKey = toEnvKey(profile);
|
|
180
|
+
const connectionKey = profileKey ? `ARCALITY_DB_${profileKey}_URL` : 'ARCALITY_DB_CONNECTION';
|
|
181
|
+
const connectionValue = envState.entries.get(connectionKey) || process.env[connectionKey] || '';
|
|
182
|
+
const mcpConfigPath = envState.entries.get('ARCALITY_DB_MCP_CONFIG') || process.env.ARCALITY_DB_MCP_CONFIG || DEFAULT_MCP_CONFIG_PATH;
|
|
183
|
+
const catalogPath = envState.entries.get('ARCALITY_DB_CATALOG') || process.env.ARCALITY_DB_CATALOG || DEFAULT_DB_CATALOG_PATH;
|
|
184
|
+
const enabledValue = String(envState.entries.get('ARCALITY_DB_ENABLED') || process.env.ARCALITY_DB_ENABLED || '').trim().toLowerCase();
|
|
185
|
+
const enabled = ['1', 'true', 'yes', 'on'].includes(enabledValue);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
provider: provider || 'postgres',
|
|
189
|
+
profile,
|
|
190
|
+
connectionKey,
|
|
191
|
+
hasConnectionString: !!connectionValue,
|
|
192
|
+
mcpConfigPath,
|
|
193
|
+
catalogPath,
|
|
194
|
+
enabled
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function ensureDbCatalogTemplate(projectRoot) {
|
|
199
|
+
const targetPath = path.join(projectRoot, DEFAULT_DB_CATALOG_PATH);
|
|
200
|
+
if (fs.existsSync(targetPath)) return false;
|
|
201
|
+
|
|
202
|
+
ensureArcalityDir(projectRoot);
|
|
203
|
+
const template = `# Arcality local database validation catalog.
|
|
204
|
+
# Keep this file free of secrets. Connection strings belong in .env.
|
|
205
|
+
|
|
206
|
+
queries:
|
|
207
|
+
example_record_by_name:
|
|
208
|
+
description: Replace this query with a real QA or Staging lookup.
|
|
209
|
+
sql: >
|
|
210
|
+
select id, name, status, created_at
|
|
211
|
+
from example_table
|
|
212
|
+
where name = $1
|
|
213
|
+
order by created_at desc
|
|
214
|
+
limit 1
|
|
215
|
+
params:
|
|
216
|
+
- name
|
|
217
|
+
redact:
|
|
218
|
+
- id
|
|
219
|
+
`;
|
|
220
|
+
fs.writeFileSync(targetPath, template, 'utf8');
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function ensureMcpConfigTemplate(projectRoot) {
|
|
225
|
+
const targetPath = path.join(projectRoot, DEFAULT_MCP_CONFIG_PATH);
|
|
226
|
+
if (fs.existsSync(targetPath)) return false;
|
|
227
|
+
|
|
228
|
+
ensureArcalityDir(projectRoot);
|
|
229
|
+
if (fs.existsSync(DEFAULT_MCP_TEMPLATE_PATH)) {
|
|
230
|
+
fs.copyFileSync(DEFAULT_MCP_TEMPLATE_PATH, targetPath);
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const fallback = {
|
|
235
|
+
command: 'npx',
|
|
236
|
+
args: ['-y', '@modelcontextprotocol/server-postgres', '${ARCALITY_DB_QA_LOCAL_URL}'],
|
|
237
|
+
toolName: 'query',
|
|
238
|
+
toolArguments: {
|
|
239
|
+
sql: '{{sql}}',
|
|
240
|
+
params: '{{params}}'
|
|
241
|
+
},
|
|
242
|
+
resultRowsPath: 'rows',
|
|
243
|
+
timeoutMs: 15000
|
|
244
|
+
};
|
|
245
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(fallback, null, 2)}\n`, 'utf8');
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function ensureQaContextFiles(projectRoot, username) {
|
|
250
|
+
const dir = ensureArcalityDir(projectRoot);
|
|
251
|
+
const contextPath = path.join(dir, 'qa-context.md');
|
|
252
|
+
const metaPath = path.join(dir, 'qa-context.meta.json');
|
|
253
|
+
let createdContext = false;
|
|
254
|
+
let createdMeta = false;
|
|
255
|
+
|
|
256
|
+
if (!fs.existsSync(contextPath)) {
|
|
257
|
+
const qaTemplate = `# Arcality QA Context
|
|
258
|
+
<!--
|
|
259
|
+
Este archivo se inyecta antes de cada mision.
|
|
260
|
+
Escribe reglas cortas, especificas y accionables.
|
|
261
|
+
Usa un bullet por regla real.
|
|
262
|
+
-->
|
|
263
|
+
|
|
264
|
+
## Reglas del Dominio
|
|
265
|
+
- Ejemplo: Un usuario no puede registrar mas de 24 horas en un mismo dia.
|
|
266
|
+
|
|
267
|
+
## Navegacion y UI Especial
|
|
268
|
+
- Ejemplo: Espera a que la tabla principal termine de cargar antes de interactuar.
|
|
269
|
+
|
|
270
|
+
## Casos Prohibidos y Anti-patrones
|
|
271
|
+
- Ejemplo: No cierres modales con Escape si existe un boton Cancelar.
|
|
272
|
+
|
|
273
|
+
## Datos y Credenciales de Prueba
|
|
274
|
+
- Ejemplo: Usa el usuario QA_PORTAL_01 para pruebas de alta.
|
|
275
|
+
|
|
276
|
+
## Resultado Esperado y Validaciones Clave
|
|
277
|
+
- Ejemplo: El registro debe aparecer en la tabla principal con estado ACTIVE.
|
|
278
|
+
`;
|
|
279
|
+
fs.writeFileSync(contextPath, qaTemplate, 'utf8');
|
|
280
|
+
createdContext = true;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!fs.existsSync(metaPath)) {
|
|
284
|
+
const metaTemplate = {
|
|
285
|
+
version: '1.0.0',
|
|
286
|
+
updated_by: username || 'qa.owner@empresa.com',
|
|
287
|
+
approved_by: '',
|
|
288
|
+
owner_team: 'QA',
|
|
289
|
+
effective_from: new Date().toISOString().slice(0, 10),
|
|
290
|
+
change_summary: 'Creacion inicial del QA Context para este proyecto.',
|
|
291
|
+
tags: ['business-rules', 'ui-navigation']
|
|
292
|
+
};
|
|
293
|
+
fs.writeFileSync(metaPath, `${JSON.stringify(metaTemplate, null, 2)}\n`, 'utf8');
|
|
294
|
+
createdMeta = true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return { createdContext, createdMeta, contextPath, metaPath };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function runDatabaseHealthCheck(projectRoot) {
|
|
301
|
+
syncEnvFromLocalFile(projectRoot);
|
|
302
|
+
const provider = resolveDatabaseProvider();
|
|
303
|
+
|
|
304
|
+
if (provider === 'mcp') {
|
|
305
|
+
const { configPath, config } = loadDatabaseMcpConfig({ projectRoot });
|
|
306
|
+
const result = await callMcpToolWithConfig(config, {
|
|
307
|
+
sql: 'select 1 as ok',
|
|
308
|
+
params: [],
|
|
309
|
+
paramsNamed: {},
|
|
310
|
+
paramsJson: '[]',
|
|
311
|
+
queryId: 'health_check',
|
|
312
|
+
validationName: 'db_health_check',
|
|
313
|
+
timeoutMs: 5000
|
|
314
|
+
});
|
|
315
|
+
const rows = extractRowsFromMcpToolResult(result, String(config.resultRowsPath || '').trim());
|
|
316
|
+
return {
|
|
317
|
+
provider: 'mcp',
|
|
318
|
+
detail: configPath,
|
|
319
|
+
rowCount: Array.isArray(rows) ? rows.length : 0
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const connection = resolveDatabaseConnectionEnv();
|
|
324
|
+
if (!connection.hasConnectionString) {
|
|
325
|
+
throw new Error(`Missing PostgreSQL connection string in ${connection.connectionEnvName}.`);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const roots = [process.env.ARCALITY_ROOT, process.cwd()].filter(Boolean);
|
|
329
|
+
let Pool = null;
|
|
330
|
+
for (const root of roots) {
|
|
331
|
+
try {
|
|
332
|
+
const requireFromRoot = createRequire(path.join(root, 'package.json'));
|
|
333
|
+
Pool = requireFromRoot('pg').Pool;
|
|
334
|
+
break;
|
|
335
|
+
} catch {}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!Pool) {
|
|
339
|
+
throw new Error('PostgreSQL driver "pg" is not installed.');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const pool = new Pool({
|
|
343
|
+
connectionString: connection.connectionString,
|
|
344
|
+
max: 1,
|
|
345
|
+
idleTimeoutMillis: 1000,
|
|
346
|
+
connectionTimeoutMillis: 5000,
|
|
347
|
+
statement_timeout: 5000
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const result = await pool.query('select 1 as ok');
|
|
352
|
+
return {
|
|
353
|
+
provider: 'postgres',
|
|
354
|
+
detail: connection.connectionEnvName,
|
|
355
|
+
rowCount: Array.isArray(result.rows) ? result.rows.length : 0
|
|
356
|
+
};
|
|
357
|
+
} finally {
|
|
358
|
+
await pool.end().catch(() => {});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function promptDatabaseHealthCheck(projectRoot, session) {
|
|
363
|
+
const wantsCheck = await confirm({
|
|
364
|
+
message: 'Deseas probar la conexion BD ahora?'
|
|
365
|
+
});
|
|
366
|
+
if (isCancel(wantsCheck) || !wantsCheck) return;
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const result = await runDatabaseHealthCheck(projectRoot);
|
|
370
|
+
pushSessionChange(session, `Health check BD exitoso via ${result.provider} (${result.detail}).`);
|
|
371
|
+
note(
|
|
372
|
+
chalk.green('Conexion BD verificada correctamente.') + '\n' +
|
|
373
|
+
chalk.white(`Proveedor: ${result.provider}`) + '\n' +
|
|
374
|
+
chalk.white(`Referencia: ${result.detail}`) + '\n' +
|
|
375
|
+
chalk.white(`Filas devueltas por health check: ${result.rowCount}`),
|
|
376
|
+
'Health Check BD'
|
|
377
|
+
);
|
|
378
|
+
} catch (error) {
|
|
379
|
+
pushSessionChange(session, `Health check BD fallido: ${error.message}`);
|
|
380
|
+
note(chalk.red(`No se pudo validar la conexion BD: ${error.message}`), 'Health Check BD');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function getQaContextState(projectRoot) {
|
|
385
|
+
const envState = readEnvMap(projectRoot);
|
|
386
|
+
const governance = String(
|
|
387
|
+
envState.entries.get('ARCALITY_QA_CONTEXT_GOVERNANCE') ||
|
|
388
|
+
process.env.ARCALITY_QA_CONTEXT_GOVERNANCE ||
|
|
389
|
+
'warn'
|
|
390
|
+
).trim().toLowerCase();
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
contextExists: fs.existsSync(path.join(projectRoot, '.arcality', 'qa-context.md')),
|
|
394
|
+
metaExists: fs.existsSync(path.join(projectRoot, '.arcality', 'qa-context.meta.json')),
|
|
395
|
+
governance: governance || 'warn'
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function formatSummary(config, projectRoot) {
|
|
400
|
+
const dbState = getDbState(projectRoot);
|
|
401
|
+
const qaState = getQaContextState(projectRoot);
|
|
402
|
+
const authConfigured = config.auth?.username && config.auth?.password ? 'si' : 'no';
|
|
403
|
+
const dbMode = dbState.enabled ? `${dbState.provider} (${dbState.profile})` : 'deshabilitada (opt-in)';
|
|
404
|
+
|
|
405
|
+
return [
|
|
406
|
+
`${chalk.bold('Proyecto:')} ${chalk.cyan(config.project?.name || 'Sin nombre')}`,
|
|
407
|
+
`${chalk.bold('Base URL:')} ${chalk.cyan(config.project?.baseUrl || 'No definida')}`,
|
|
408
|
+
`${chalk.bold('Autenticacion:')} ${chalk.white(authConfigured)}`,
|
|
409
|
+
`${chalk.bold('Framework:')} ${chalk.magenta(config.project?.frameworkDetected || 'N/A')}`,
|
|
410
|
+
`${chalk.bold('Misiones YAML:')} ${chalk.yellow(config.runtime?.yamlOutputDir || './.arcality')}`,
|
|
411
|
+
`${chalk.bold('Validacion BD:')} ${chalk.white(dbMode)}`,
|
|
412
|
+
`${chalk.bold('QA Context:')} ${chalk.white(`contexto=${qaState.contextExists ? 'si' : 'no'}, metadata=${qaState.metaExists ? 'si' : 'no'}, governance=${qaState.governance}`)}`
|
|
413
|
+
].join('\n');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function getConfigDiffLines(originalConfig, draftConfig) {
|
|
417
|
+
const lines = [];
|
|
418
|
+
|
|
419
|
+
if ((originalConfig.project?.name || '') !== (draftConfig.project?.name || '')) {
|
|
420
|
+
lines.push(`Proyecto.nombre: ${originalConfig.project?.name || 'N/A'} -> ${draftConfig.project?.name || 'N/A'}`);
|
|
421
|
+
}
|
|
422
|
+
if ((originalConfig.project?.baseUrl || '') !== (draftConfig.project?.baseUrl || '')) {
|
|
423
|
+
lines.push(`Proyecto.baseUrl: ${originalConfig.project?.baseUrl || 'N/A'} -> ${draftConfig.project?.baseUrl || 'N/A'}`);
|
|
424
|
+
}
|
|
425
|
+
if ((originalConfig.project?.frameworkDetected || '') !== (draftConfig.project?.frameworkDetected || '')) {
|
|
426
|
+
lines.push(`Proyecto.frameworkDetected: ${originalConfig.project?.frameworkDetected || 'N/A'} -> ${draftConfig.project?.frameworkDetected || 'N/A'}`);
|
|
427
|
+
}
|
|
428
|
+
if ((originalConfig.auth?.username || '') !== (draftConfig.auth?.username || '')) {
|
|
429
|
+
lines.push(`Auth.username: ${originalConfig.auth?.username || 'N/A'} -> ${draftConfig.auth?.username || 'N/A'}`);
|
|
430
|
+
}
|
|
431
|
+
if ((originalConfig.auth?.password || '') !== (draftConfig.auth?.password || '')) {
|
|
432
|
+
lines.push('Auth.password: actualizada');
|
|
433
|
+
}
|
|
434
|
+
if ((originalConfig.runtime?.yamlOutputDir || './.arcality') !== (draftConfig.runtime?.yamlOutputDir || './.arcality')) {
|
|
435
|
+
lines.push(`Runtime.yamlOutputDir: ${originalConfig.runtime?.yamlOutputDir || './.arcality'} -> ${draftConfig.runtime?.yamlOutputDir || './.arcality'}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return lines;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function createSessionState() {
|
|
442
|
+
return {
|
|
443
|
+
changes: []
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function pushSessionChange(session, change) {
|
|
448
|
+
if (!change) return;
|
|
449
|
+
session.changes.push(change);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function buildPendingChanges(session, originalConfig, draftConfig) {
|
|
453
|
+
const lines = [];
|
|
454
|
+
const configDiffLines = getConfigDiffLines(originalConfig, draftConfig);
|
|
455
|
+
|
|
456
|
+
if (configDiffLines.length > 0) {
|
|
457
|
+
lines.push('Cambios pendientes en arcality.config:');
|
|
458
|
+
for (const line of configDiffLines) {
|
|
459
|
+
lines.push(`- ${line}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (session.changes.length > 0) {
|
|
464
|
+
lines.push(configDiffLines.length > 0 ? '' : 'Cambios aplicados en esta sesion:');
|
|
465
|
+
if (configDiffLines.length > 0) {
|
|
466
|
+
lines.push('');
|
|
467
|
+
lines.push('Cambios auxiliares (.env y archivos locales):');
|
|
468
|
+
}
|
|
469
|
+
for (const change of session.changes) {
|
|
470
|
+
lines.push(`- ${change}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return lines.filter((line, index, arr) => !(line === '' && arr[index - 1] === '')).join('\n').trim();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async function editProjectSection(config, projectRoot, session) {
|
|
478
|
+
const projectName = await text({
|
|
479
|
+
message: 'Nombre del proyecto:',
|
|
480
|
+
initialValue: config.project?.name || '',
|
|
481
|
+
validate: value => value && value.trim().length >= 2 ? undefined : 'Ingresa al menos 2 caracteres.'
|
|
482
|
+
});
|
|
483
|
+
if (isCancel(projectName)) return;
|
|
484
|
+
|
|
485
|
+
const baseUrl = await text({
|
|
486
|
+
message: 'Base URL de la aplicacion:',
|
|
487
|
+
initialValue: config.project?.baseUrl || '',
|
|
488
|
+
validate: value => isValidUrl(value) ? undefined : 'Usa una URL valida con http:// o https://'
|
|
489
|
+
});
|
|
490
|
+
if (isCancel(baseUrl)) return;
|
|
491
|
+
|
|
492
|
+
const redetect = await confirm({
|
|
493
|
+
message: 'Deseas redetectar el framework local?'
|
|
494
|
+
});
|
|
495
|
+
if (isCancel(redetect)) return;
|
|
496
|
+
|
|
497
|
+
config.project = config.project || {};
|
|
498
|
+
config.project.name = projectName.trim();
|
|
499
|
+
config.project.baseUrl = baseUrl.trim();
|
|
500
|
+
if (redetect) {
|
|
501
|
+
config.project.frameworkDetected = detectFramework(projectRoot);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
pushSessionChange(session, 'Se recalibro la seccion Proyecto.');
|
|
505
|
+
note(chalk.green('Proyecto actualizado en borrador.'), 'Resumen');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async function editAuthSection(config, session) {
|
|
509
|
+
const needsLogin = await confirm({
|
|
510
|
+
message: 'La aplicacion requiere autenticacion?'
|
|
511
|
+
});
|
|
512
|
+
if (isCancel(needsLogin)) return;
|
|
513
|
+
|
|
514
|
+
config.auth = config.auth || {};
|
|
515
|
+
|
|
516
|
+
if (!needsLogin) {
|
|
517
|
+
config.auth.username = '';
|
|
518
|
+
config.auth.password = '';
|
|
519
|
+
pushSessionChange(session, 'Se deshabilito la autenticacion en arcality.config.');
|
|
520
|
+
note(chalk.green('Autenticacion limpiada en borrador.'), 'Resumen');
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const username = await text({
|
|
525
|
+
message: 'Usuario o correo de acceso:',
|
|
526
|
+
initialValue: config.auth.username || '',
|
|
527
|
+
validate: value => value && value.trim() ? undefined : 'El usuario es obligatorio.'
|
|
528
|
+
});
|
|
529
|
+
if (isCancel(username)) return;
|
|
530
|
+
|
|
531
|
+
const password = await passwordPrompt({
|
|
532
|
+
message: config.auth.password
|
|
533
|
+
? 'Nueva contrasena (deja vacio para conservar la actual):'
|
|
534
|
+
: 'Contrasena de acceso:',
|
|
535
|
+
validate: value => {
|
|
536
|
+
if (config.auth.password && !value) return undefined;
|
|
537
|
+
return value && value.length ? undefined : 'La contrasena es obligatoria.';
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
if (isCancel(password)) return;
|
|
541
|
+
|
|
542
|
+
config.auth.username = username.trim();
|
|
543
|
+
if (password) {
|
|
544
|
+
config.auth.password = password;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
pushSessionChange(session, 'Se recalibro la seccion Autenticacion.');
|
|
548
|
+
note(chalk.green('Autenticacion actualizada en borrador.'), 'Resumen');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function editRuntimeSection(config, session) {
|
|
552
|
+
const yamlOutputDir = await text({
|
|
553
|
+
message: 'Carpeta base para YAML, reportes y contexto:',
|
|
554
|
+
initialValue: config.runtime?.yamlOutputDir || './.arcality',
|
|
555
|
+
validate: value => value && value.trim() ? undefined : 'La carpeta no puede quedar vacia.'
|
|
556
|
+
});
|
|
557
|
+
if (isCancel(yamlOutputDir)) return;
|
|
558
|
+
|
|
559
|
+
config.runtime = config.runtime || {};
|
|
560
|
+
config.runtime.yamlOutputDir = yamlOutputDir.trim();
|
|
561
|
+
config.runtime.reuseSuccessfulYamls = true;
|
|
562
|
+
config.runtime.singleConfigurationMode = true;
|
|
563
|
+
|
|
564
|
+
pushSessionChange(session, 'Se actualizo el runtime y la carpeta base de misiones.');
|
|
565
|
+
note(chalk.green('Runtime actualizado en borrador.'), 'Resumen');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async function editDatabaseSection(projectRoot, session) {
|
|
569
|
+
const current = getDbState(projectRoot);
|
|
570
|
+
const wantsDatabaseValidation = await confirm({
|
|
571
|
+
message: current.enabled
|
|
572
|
+
? 'Deseas mantener habilitada la validacion BD para este proyecto?'
|
|
573
|
+
: 'Este proyecto requiere validacion BD local?'
|
|
574
|
+
});
|
|
575
|
+
if (isCancel(wantsDatabaseValidation)) return;
|
|
576
|
+
|
|
577
|
+
if (!wantsDatabaseValidation) {
|
|
578
|
+
writeEnvUpdates(projectRoot, {
|
|
579
|
+
ARCALITY_DB_ENABLED: 'false'
|
|
580
|
+
});
|
|
581
|
+
pushSessionChange(session, 'Se deshabilito la validacion BD en .env.');
|
|
582
|
+
note(
|
|
583
|
+
chalk.green('Validacion BD deshabilitada para este proyecto.') + '\n' +
|
|
584
|
+
chalk.white('No se pedira connection string ni configuracion MCP mientras siga apagada.'),
|
|
585
|
+
'Base de datos'
|
|
586
|
+
);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const mode = await select({
|
|
591
|
+
message: 'Como deseas conectar la corroboracion BD?',
|
|
592
|
+
options: [
|
|
593
|
+
{ label: 'PostgreSQL directo (.env)', value: 'postgres' },
|
|
594
|
+
{ label: 'MCP PostgreSQL (.env + mcp.postgres.json)', value: 'mcp' }
|
|
595
|
+
],
|
|
596
|
+
initialValue: current.enabled ? current.provider : 'postgres'
|
|
597
|
+
});
|
|
598
|
+
if (isCancel(mode)) return;
|
|
599
|
+
|
|
600
|
+
const profile = await text({
|
|
601
|
+
message: 'Perfil logico de BD:',
|
|
602
|
+
initialValue: current.profile || 'qa_local',
|
|
603
|
+
validate: value => value && value.trim() ? undefined : 'El perfil es obligatorio.'
|
|
604
|
+
});
|
|
605
|
+
if (isCancel(profile)) return;
|
|
606
|
+
|
|
607
|
+
const profileKey = toEnvKey(profile);
|
|
608
|
+
const connectionKey = profileKey ? `ARCALITY_DB_${profileKey}_URL` : current.connectionKey;
|
|
609
|
+
const connectionString = await passwordPrompt({
|
|
610
|
+
message: current.hasConnectionString
|
|
611
|
+
? `Connection string para ${connectionKey} (deja vacio para conservar la actual):`
|
|
612
|
+
: `Connection string para ${connectionKey}:`,
|
|
613
|
+
validate: value => {
|
|
614
|
+
if (current.hasConnectionString && !value) return undefined;
|
|
615
|
+
return value && value.trim() ? undefined : 'La connection string es obligatoria.';
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
if (isCancel(connectionString)) return;
|
|
619
|
+
|
|
620
|
+
const catalogCreated = ensureDbCatalogTemplate(projectRoot);
|
|
621
|
+
|
|
622
|
+
const updates = {
|
|
623
|
+
ARCALITY_DB_ENABLED: 'true',
|
|
624
|
+
ARCALITY_DB_PROVIDER: mode,
|
|
625
|
+
ARCALITY_DB_PROFILE: profile.trim(),
|
|
626
|
+
ARCALITY_DB_CATALOG: DEFAULT_DB_CATALOG_PATH
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
if (connectionString && connectionString.trim()) {
|
|
630
|
+
updates[connectionKey] = connectionString.trim();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
let mcpCreated = false;
|
|
634
|
+
if (mode === 'mcp') {
|
|
635
|
+
mcpCreated = ensureMcpConfigTemplate(projectRoot);
|
|
636
|
+
updates.ARCALITY_DB_MCP_CONFIG = DEFAULT_MCP_CONFIG_PATH;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
writeEnvUpdates(projectRoot, updates);
|
|
640
|
+
|
|
641
|
+
pushSessionChange(session, `Se habilito validacion BD en modo ${mode} con perfil ${profile.trim()}.`);
|
|
642
|
+
pushSessionChange(session, `Se actualizo la variable ${connectionKey} en .env${connectionString ? '' : ' (se conservo el valor actual)'}.`);
|
|
643
|
+
if (catalogCreated) {
|
|
644
|
+
pushSessionChange(session, `Se creo ${DEFAULT_DB_CATALOG_PATH}.`);
|
|
645
|
+
}
|
|
646
|
+
if (mcpCreated) {
|
|
647
|
+
pushSessionChange(session, `Se creo ${DEFAULT_MCP_CONFIG_PATH}.`);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
note(
|
|
651
|
+
chalk.green(`Validacion BD configurada en modo ${mode}.`) + '\n' +
|
|
652
|
+
chalk.white(`Archivo de catalogo: ${DEFAULT_DB_CATALOG_PATH}`) + '\n' +
|
|
653
|
+
(mode === 'mcp' ? chalk.white(`Archivo MCP: ${DEFAULT_MCP_CONFIG_PATH}`) : chalk.white(`Variable de conexion: ${connectionKey}`)),
|
|
654
|
+
'Base de datos'
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
await promptDatabaseHealthCheck(projectRoot, session);
|
|
658
|
+
}
|
|
659
|
+
async function editQaContextSection(projectRoot, username, session) {
|
|
660
|
+
const action = await select({
|
|
661
|
+
message: 'QA Context y gobernanza:',
|
|
662
|
+
options: [
|
|
663
|
+
{ label: 'Crear archivos base si faltan', value: 'bootstrap' },
|
|
664
|
+
{ label: 'Cambiar modo de gobernanza', value: 'governance' }
|
|
665
|
+
]
|
|
666
|
+
});
|
|
667
|
+
if (isCancel(action)) return;
|
|
668
|
+
|
|
669
|
+
if (action === 'bootstrap') {
|
|
670
|
+
const result = ensureQaContextFiles(projectRoot, username);
|
|
671
|
+
if (result.createdContext) {
|
|
672
|
+
pushSessionChange(session, `Se creo ${path.relative(projectRoot, result.contextPath)}.`);
|
|
673
|
+
}
|
|
674
|
+
if (result.createdMeta) {
|
|
675
|
+
pushSessionChange(session, `Se creo ${path.relative(projectRoot, result.metaPath)}.`);
|
|
676
|
+
}
|
|
677
|
+
note(
|
|
678
|
+
[
|
|
679
|
+
result.createdContext ? `Se creo ${path.relative(projectRoot, result.contextPath)}` : `Ya existe ${path.relative(projectRoot, result.contextPath)}`,
|
|
680
|
+
result.createdMeta ? `Se creo ${path.relative(projectRoot, result.metaPath)}` : `Ya existe ${path.relative(projectRoot, result.metaPath)}`
|
|
681
|
+
].join('\n'),
|
|
682
|
+
'QA Context'
|
|
683
|
+
);
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const governance = await select({
|
|
688
|
+
message: 'Modo de gobernanza:',
|
|
689
|
+
options: [
|
|
690
|
+
{ label: 'warn', value: 'warn' },
|
|
691
|
+
{ label: 'strict', value: 'strict' },
|
|
692
|
+
{ label: 'off', value: 'off' }
|
|
693
|
+
],
|
|
694
|
+
initialValue: getQaContextState(projectRoot).governance
|
|
695
|
+
});
|
|
696
|
+
if (isCancel(governance)) return;
|
|
697
|
+
|
|
698
|
+
writeEnvUpdates(projectRoot, {
|
|
699
|
+
ARCALITY_QA_CONTEXT_GOVERNANCE: governance
|
|
700
|
+
});
|
|
701
|
+
pushSessionChange(session, `Se actualizo ARCALITY_QA_CONTEXT_GOVERNANCE=${governance} en .env.`);
|
|
702
|
+
note(chalk.green(`Gobernanza actualizada a ${governance} en .env.`), 'QA Context');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function reviewAndConfirmSave(session, originalConfig, draftConfig) {
|
|
706
|
+
const summary = buildPendingChanges(session, originalConfig, draftConfig);
|
|
707
|
+
|
|
708
|
+
if (!summary) {
|
|
709
|
+
note(chalk.yellow('No hay cambios pendientes por guardar.'), 'Revision');
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
note(summary, 'Revision de cambios');
|
|
714
|
+
|
|
715
|
+
const shouldSave = await confirm({
|
|
716
|
+
message: 'Deseas guardar estos cambios?'
|
|
717
|
+
});
|
|
718
|
+
if (isCancel(shouldSave) || !shouldSave) {
|
|
719
|
+
note(chalk.cyan('Se conservaron los cambios en borrador, pero no se guardaron todavia.'), 'Revision');
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
async function main() {
|
|
727
|
+
const projectRoot = process.cwd();
|
|
728
|
+
|
|
729
|
+
if (!configExists(projectRoot)) {
|
|
730
|
+
note(
|
|
731
|
+
chalk.yellow('No existe arcality.config en este proyecto.') + '\n' +
|
|
732
|
+
chalk.white('Primero ejecuta la configuracion inicial completa con ') + chalk.cyan('npx arcality init'),
|
|
733
|
+
'Configuracion faltante'
|
|
734
|
+
);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const originalConfig = loadProjectConfig(projectRoot);
|
|
739
|
+
if (!originalConfig) {
|
|
740
|
+
note(chalk.red('No se pudo leer arcality.config.'), 'Error');
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const draftConfig = deepClone(originalConfig);
|
|
745
|
+
const session = createSessionState();
|
|
746
|
+
|
|
747
|
+
console.clear();
|
|
748
|
+
intro(chalk.cyan('Arcality Config Editor'));
|
|
749
|
+
|
|
750
|
+
let shouldSave = false;
|
|
751
|
+
|
|
752
|
+
while (true) {
|
|
753
|
+
const pendingChanges = buildPendingChanges(session, originalConfig, draftConfig);
|
|
754
|
+
note(
|
|
755
|
+
formatSummary(draftConfig, projectRoot) +
|
|
756
|
+
(pendingChanges ? `\n\n${chalk.bold('Cambios pendientes:')} ${chalk.yellow(String(session.changes.length + getConfigDiffLines(originalConfig, draftConfig).length))}` : ''),
|
|
757
|
+
'Estado actual'
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
const action = await select({
|
|
761
|
+
message: 'Que deseas recalibrar?',
|
|
762
|
+
options: [
|
|
763
|
+
{ label: 'Proyecto (nombre, URL, framework)', value: 'project' },
|
|
764
|
+
{ label: 'Autenticacion', value: 'auth' },
|
|
765
|
+
{ label: 'Runtime y carpeta de misiones', value: 'runtime' },
|
|
766
|
+
{ label: 'Validacion BD', value: 'db' },
|
|
767
|
+
{ label: 'Probar conexion BD actual', value: 'db_check' },
|
|
768
|
+
{ label: 'QA Context y gobernanza', value: 'qa' },
|
|
769
|
+
{ label: 'Revisar cambios pendientes', value: 'review' },
|
|
770
|
+
{ label: 'Guardar cambios y salir', value: 'save' },
|
|
771
|
+
{ label: 'Salir sin guardar', value: 'exit' }
|
|
772
|
+
]
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
if (isCancel(action) || action === 'exit') {
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (action === 'project') {
|
|
780
|
+
await editProjectSection(draftConfig, projectRoot, session);
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (action === 'auth') {
|
|
785
|
+
await editAuthSection(draftConfig, session);
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (action === 'runtime') {
|
|
790
|
+
await editRuntimeSection(draftConfig, session);
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (action === 'db') {
|
|
795
|
+
await editDatabaseSection(projectRoot, session);
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (action === 'db_check') {
|
|
800
|
+
await promptDatabaseHealthCheck(projectRoot, session);
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (action === 'qa') {
|
|
805
|
+
await editQaContextSection(projectRoot, draftConfig.auth?.username || '', session);
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (action === 'review') {
|
|
810
|
+
const summary = buildPendingChanges(session, originalConfig, draftConfig);
|
|
811
|
+
note(summary || chalk.cyan('Aun no hay cambios pendientes en esta sesion.'), 'Revision de cambios');
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (action === 'save') {
|
|
816
|
+
shouldSave = await reviewAndConfirmSave(session, originalConfig, draftConfig);
|
|
817
|
+
if (shouldSave) break;
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (!shouldSave) {
|
|
823
|
+
cancel('No se guardaron cambios en arcality.config.');
|
|
824
|
+
process.exit(0);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
saveProjectConfig(draftConfig, projectRoot);
|
|
828
|
+
|
|
829
|
+
note(
|
|
830
|
+
chalk.green('Configuracion actualizada correctamente.') + '\n' +
|
|
831
|
+
chalk.white(`Proyecto: ${draftConfig.project?.name || 'N/A'}`) + '\n' +
|
|
832
|
+
chalk.white(`Base URL: ${draftConfig.project?.baseUrl || 'N/A'}`) + '\n' +
|
|
833
|
+
chalk.white(`YAML base: ${draftConfig.runtime?.yamlOutputDir || './.arcality'}`),
|
|
834
|
+
'Cambios guardados'
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
outro(chalk.cyan('Listo. Ya puedes seguir usando Arcality con la configuracion recalibrada.'));
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
main().catch(error => {
|
|
841
|
+
console.error(chalk.red('Error al editar la configuracion:'), error.message);
|
|
842
|
+
process.exit(1);
|
|
843
|
+
});
|