agent-rev 0.5.13 → 0.5.23

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.
@@ -202,6 +202,110 @@ function extractJson(text) {
202
202
  throw new Error('No JSON found');
203
203
  return JSON.parse(c.slice(first, last + 1));
204
204
  }
205
+ /** Read a file safely, truncating to maxChars. Returns null if missing or empty. */
206
+ async function readFileSafe(p, maxChars) {
207
+ try {
208
+ const content = await fs.readFile(p, 'utf-8');
209
+ if (!content || !content.trim())
210
+ return null;
211
+ if (content.length > maxChars) {
212
+ return content.slice(0, maxChars) + `\n... [truncado, ${content.length - maxChars} caracteres mas]`;
213
+ }
214
+ return content;
215
+ }
216
+ catch {
217
+ return null;
218
+ }
219
+ }
220
+ const PREFETCH_SKIP_DIRS = new Set([
221
+ '.agent', 'node_modules', '.git', 'dist', 'build', 'target',
222
+ '__pycache__', '.venv', 'venv', '.idea', '.vscode', '.next',
223
+ 'coverage', '.cache', 'out', 'bin', 'obj',
224
+ ]);
225
+ /** Walk a component dir looking for entry point, controllers, routers, schemas, models. */
226
+ async function walkForKeyFiles(root) {
227
+ const result = {
228
+ entry: null,
229
+ controllers: [],
230
+ schemas: [],
231
+ };
232
+ const SOURCE_EXT = /\.(ts|tsx|js|jsx|py|go|java|kt|rs|cs)$/;
233
+ const SKIP_FILE = /\.(test|spec|d)\.[a-z]+$/i;
234
+ const ENTRY_NAMES = new Set([
235
+ 'main.ts', 'main.js', 'main.py', 'main.go', 'Main.java',
236
+ 'index.ts', 'index.js', 'index.py',
237
+ 'app.ts', 'app.js', 'app.py',
238
+ 'server.ts', 'server.js', 'server.py',
239
+ 'application.ts', 'application.js', 'application.py',
240
+ ]);
241
+ const ENTRY_REGEX = /Application\.[a-z]+$/i; // *Application.java, *Application.kt, etc.
242
+ const CTRL_REGEX = /(controller|router|routes|handler|endpoint|resource)/i;
243
+ const SCHEMA_REGEX = /(schema|\.entity|\.model|\.dto|models?\.|entities?\.)/i;
244
+ // Directories that conventionally hold data/domain classes (language-agnostic)
245
+ const DOMAIN_DIR_REGEX = /[\/\\](domain|entity|entities|model|models|dto|dtos|schema|schemas)[\/\\]/i;
246
+ async function walk(dir, depth) {
247
+ if (depth > 6)
248
+ return;
249
+ if (result.controllers.length >= 5 && result.schemas.length >= 5 && result.entry)
250
+ return;
251
+ let entries;
252
+ try {
253
+ entries = await fs.readdir(dir, { withFileTypes: true });
254
+ }
255
+ catch {
256
+ return;
257
+ }
258
+ for (const e of entries) {
259
+ if (e.name.startsWith('.'))
260
+ continue;
261
+ if (PREFETCH_SKIP_DIRS.has(e.name))
262
+ continue;
263
+ const p = path.join(dir, e.name);
264
+ if (e.isDirectory()) {
265
+ await walk(p, depth + 1);
266
+ continue;
267
+ }
268
+ if (!e.isFile())
269
+ continue;
270
+ if (!SOURCE_EXT.test(e.name))
271
+ continue;
272
+ if (SKIP_FILE.test(e.name))
273
+ continue;
274
+ // entry point detection
275
+ if (!result.entry && (ENTRY_NAMES.has(e.name) || ENTRY_REGEX.test(e.name))) {
276
+ result.entry = p;
277
+ }
278
+ if (CTRL_REGEX.test(e.name) && result.controllers.length < 5) {
279
+ result.controllers.push(p);
280
+ }
281
+ const isSchema = SCHEMA_REGEX.test(e.name) || DOMAIN_DIR_REGEX.test(p);
282
+ if (isSchema && result.schemas.length < 5) {
283
+ result.schemas.push(p);
284
+ }
285
+ }
286
+ }
287
+ await walk(root, 0);
288
+ return result;
289
+ }
290
+ /** Detect if the LLM hallucinated tool calls instead of producing the document content. */
291
+ function looksLikeHallucinatedToolCalls(text) {
292
+ if (!text)
293
+ return true;
294
+ // Common hallucination patterns when the LLM thinks it's calling tools
295
+ const TOOL_PATTERNS = [
296
+ /===\s*TOOL_CALL\s*:/i,
297
+ /^\s*<tool_call>/m,
298
+ /^\s*<function_call>/m,
299
+ /^\s*```tool_use/im,
300
+ ];
301
+ if (TOOL_PATTERNS.some(re => re.test(text)))
302
+ return true;
303
+ // If the text has NO valid file markers at all, it's also a failure case
304
+ const fileMarkerMatches = text.match(/===\s+[^\n=]+?\.md\s*===/g);
305
+ if (!fileMarkerMatches || fileMarkerMatches.length === 0)
306
+ return true;
307
+ return false;
308
+ }
205
309
  export class AgentEngine {
206
310
  config;
207
311
  projectDir;
@@ -516,9 +620,16 @@ INSTRUCCIONES:
516
620
  const ROLE_BINARIES = new Set(['agent-orch', 'agent-impl', 'agent-rev', 'agent-explorer']);
517
621
  const tryRoleBinaryCreds = async (cliName, model) => {
518
622
  const credsPath = path.join(os.homedir(), `.${cliName}`, 'oauth_creds.json');
519
- if (!(await fileExists(credsPath))) {
520
- log.warn(`${cliName} has no credentials — run: ${cliName} --login`);
521
- return null;
623
+ const hasOAuthCreds = await fileExists(credsPath);
624
+ if (!hasOAuthCreds) {
625
+ // No role OAuth creds — try global API key config before giving up
626
+ const { loadApiKeyConfig } = await import('../utils/qwen-auth.js');
627
+ const apiKeyCfg = await loadApiKeyConfig();
628
+ if (!apiKeyCfg) {
629
+ log.warn(`${cliName} has no credentials — run: agent-mp setup api-key or ${cliName} --login`);
630
+ return null;
631
+ }
632
+ // Fall through: callQwenAPIFromCreds will use the API key config
522
633
  }
523
634
  const sp = this._startSpinner(`${cliName} ${model}`);
524
635
  let lineBuf = '';
@@ -969,6 +1080,114 @@ INSTRUCCIONES:
969
1080
  }
970
1081
  }
971
1082
  }
1083
+ /**
1084
+ * Pre-read the actual file contents (manifests, env, entry points, controllers, schemas)
1085
+ * for every component in the project, so the LLM does not need tool-use to inspect them.
1086
+ * This is critical when the explorer runs against the Qwen API (no tools available)
1087
+ * to avoid the LLM hallucinating tool calls instead of producing the real document.
1088
+ */
1089
+ async _prefetchProjectFiles() {
1090
+ const MAX_MANIFEST = 5000;
1091
+ const MAX_ENV = 2000;
1092
+ const MAX_README = 3000;
1093
+ const MAX_ENTRY = 3000;
1094
+ const MAX_CTRL = 2500;
1095
+ const MAX_SCHEMA = 1800;
1096
+ let topEntries;
1097
+ try {
1098
+ topEntries = await fs.readdir(this.projectDir, { withFileTypes: true });
1099
+ }
1100
+ catch {
1101
+ return '';
1102
+ }
1103
+ const sections = [];
1104
+ // Top-level manifest / readme / docker (gives global context)
1105
+ const TOP_GLOBAL_FILES = ['package.json', 'pom.xml', 'build.gradle', 'requirements.txt', 'go.mod', 'Cargo.toml', 'README.md', 'docker-compose.yml', 'docker-compose.yaml', 'Makefile'];
1106
+ const topGlobal = [];
1107
+ for (const fname of TOP_GLOBAL_FILES) {
1108
+ const content = await readFileSafe(path.join(this.projectDir, fname), MAX_MANIFEST);
1109
+ if (content)
1110
+ topGlobal.push(`### ROOT: ${fname}\n\`\`\`\n${content}\n\`\`\``);
1111
+ }
1112
+ if (topGlobal.length > 0) {
1113
+ sections.push(`## CONTEXTO GLOBAL DEL PROYECTO\n\n${topGlobal.join('\n\n')}`);
1114
+ }
1115
+ // Each top-level directory = candidate component
1116
+ const components = topEntries
1117
+ .filter(e => e.isDirectory() && !PREFETCH_SKIP_DIRS.has(e.name) && !e.name.startsWith('.'))
1118
+ .sort((a, b) => a.name.localeCompare(b.name));
1119
+ for (const comp of components) {
1120
+ const compDir = path.join(this.projectDir, comp.name);
1121
+ const compSection = [`## COMPONENTE: ${comp.name}`];
1122
+ // Manifest
1123
+ const MANIFESTS = ['package.json', 'requirements.txt', 'pyproject.toml', 'pom.xml', 'build.gradle', 'build.gradle.kts', 'Cargo.toml', 'go.mod', 'composer.json', 'Gemfile'];
1124
+ let manifestFound = false;
1125
+ for (const m of MANIFESTS) {
1126
+ const content = await readFileSafe(path.join(compDir, m), MAX_MANIFEST);
1127
+ if (content) {
1128
+ compSection.push(`### MANIFEST: ${m}\n\`\`\`\n${content}\n\`\`\``);
1129
+ manifestFound = true;
1130
+ break;
1131
+ }
1132
+ }
1133
+ // README
1134
+ const readme = await readFileSafe(path.join(compDir, 'README.md'), MAX_README)
1135
+ || await readFileSafe(path.join(compDir, 'readme.md'), MAX_README);
1136
+ if (readme)
1137
+ compSection.push(`### README\n${readme}`);
1138
+ // Env / config files (try several conventional locations)
1139
+ const ENV_FILES = ['.env', '.env.example', '.env.sample', '.env.local', '.env.dev', 'application.yml', 'application.yaml', 'application.properties', 'config.yaml', 'config.yml'];
1140
+ const ENV_DIRS = [compDir, path.join(compDir, 'src', 'main', 'resources'), path.join(compDir, 'config'), path.join(compDir, 'src', 'config')];
1141
+ const seenEnv = new Set();
1142
+ let envCount = 0;
1143
+ for (const dir of ENV_DIRS) {
1144
+ for (const f of ENV_FILES) {
1145
+ if (envCount >= 3)
1146
+ break;
1147
+ const p = path.join(dir, f);
1148
+ if (seenEnv.has(p))
1149
+ continue;
1150
+ seenEnv.add(p);
1151
+ const content = await readFileSafe(p, MAX_ENV);
1152
+ if (content) {
1153
+ const rel = path.relative(compDir, p);
1154
+ compSection.push(`### CONFIG/ENV: ${rel}\n\`\`\`\n${content}\n\`\`\``);
1155
+ envCount++;
1156
+ }
1157
+ }
1158
+ }
1159
+ // Walk source for entry, controllers, schemas
1160
+ const found = await walkForKeyFiles(compDir);
1161
+ if (found.entry) {
1162
+ const content = await readFileSafe(found.entry, MAX_ENTRY);
1163
+ if (content) {
1164
+ const rel = path.relative(compDir, found.entry);
1165
+ compSection.push(`### ENTRY POINT: ${rel}\n\`\`\`\n${content}\n\`\`\``);
1166
+ }
1167
+ }
1168
+ const ctrls = found.controllers.slice(0, 5);
1169
+ for (const ctrl of ctrls) {
1170
+ const content = await readFileSafe(ctrl, MAX_CTRL);
1171
+ if (content) {
1172
+ const rel = path.relative(compDir, ctrl);
1173
+ compSection.push(`### CONTROLLER/ROUTER: ${rel}\n\`\`\`\n${content}\n\`\`\``);
1174
+ }
1175
+ }
1176
+ const schemas = found.schemas.slice(0, 4);
1177
+ for (const sch of schemas) {
1178
+ const content = await readFileSafe(sch, MAX_SCHEMA);
1179
+ if (content) {
1180
+ const rel = path.relative(compDir, sch);
1181
+ compSection.push(`### SCHEMA/MODEL: ${rel}\n\`\`\`\n${content}\n\`\`\``);
1182
+ }
1183
+ }
1184
+ // Only add the component if we read at least the manifest or one source file
1185
+ if (manifestFound || found.entry || found.controllers.length > 0 || found.schemas.length > 0) {
1186
+ sections.push(compSection.join('\n\n'));
1187
+ }
1188
+ }
1189
+ return sections.join('\n\n---\n\n');
1190
+ }
972
1191
  async runExplorer(task) {
973
1192
  if (!this.config.roles.explorer) {
974
1193
  if (!this.config.fallback_global) {
@@ -981,10 +1200,32 @@ INSTRUCCIONES:
981
1200
  // Ensure .agent/ structure exists
982
1201
  const agentDir = path.join(this.projectDir, '.agent');
983
1202
  const contextDir = path.join(agentDir, 'context');
1203
+ const docsDir = path.join(agentDir, 'docs');
1204
+ const rulesDir = path.join(agentDir, 'rules');
984
1205
  await fs.mkdir(contextDir, { recursive: true });
985
- await fs.mkdir(path.join(agentDir, 'rules'), { recursive: true });
986
- // Clean up stray files before running
987
- await this._cleanContextDir(contextDir);
1206
+ await fs.mkdir(docsDir, { recursive: true });
1207
+ await fs.mkdir(rulesDir, { recursive: true });
1208
+ // Seed rules files if they don't exist yet (idempotent — never overwrites)
1209
+ const proj = this.config.project;
1210
+ const rulesSeeds = {
1211
+ 'workflow.md': `# Workflow Contract — ${proj}\n\n\`\`\`\n[ORCHESTRATOR] planifica → genera plan.json\n ↓\n[IMPLEMENTOR] ejecuta el plan → modifica archivos\n ↓\n[REVIEWER] valida → genera result.md\n\`\`\`\n\n## REGLA #1 — Separacion de roles\nCada CLI opera SOLO en su rol.\n\n## REGLA #2 — Orden estricto\nOrchestrator primero. Implementor no actua sin plan. Reviewer no actua sin implementacion.\n\n## REGLA #3 — Sin inventar\nCada rol trabaja con los archivos reales del proyecto.\n`,
1212
+ 'orchestrator.md': `# Rol: ORCHESTRATOR — ${proj}\n\n## Responsabilidad\nAnalizar el requerimiento, leer el contexto y generar un plan de tareas ejecutable.\n\n## Antes de planificar\n1. Leer .agent/context/architecture.md\n2. Leer .agent/rules/structure.md\n3. Leer .agent/rules/patterns.md\n\n## Al generar el plan\n- Pasos atomicos (1 paso = 1 cambio verificable)\n- Rutas de archivos REALES\n- Dependencias entre pasos si las hay\n\n## Prohibido\n- Modificar archivos de codigo directamente\n- Ejecutar comandos de build o test\n`,
1213
+ 'implementor.md': `# Rol: IMPLEMENTOR — ${proj}\n\n## Responsabilidad\nEjecutar el plan del Orchestrator. Modificar archivos de codigo siguiendo las instrucciones exactas.\n\n## Antes de implementar\n1. Leer .agent/tasks/{ID}/plan.json completo\n2. Leer .agent/rules/structure.md\n3. Leer .agent/rules/patterns.md\n\n## Al implementar\n- Seguir el plan paso a paso, en orden\n- No agregar cambios fuera del scope\n- Marcar cada paso como completado\n\n## Prohibido\n- Modificar el plan\n- Hacer refactors no solicitados\n`,
1214
+ 'reviewer.md': `# Rol: REVIEWER — ${proj}\n\n## Responsabilidad\nValidar que la implementacion cumple el plan y respeta las reglas del proyecto.\n\n## Checklist\n- [ ] Cada paso del plan fue implementado\n- [ ] Sin archivos modificados fuera del scope\n- [ ] Convenciones de patterns.md respetadas\n- [ ] Estructura de structure.md respetada\n- [ ] Sin codigo muerto ni imports sin usar\n\n## Al generar result.md\n- Indicar cada item: OK / FAIL / SKIP con razon\n- Si FAILs: describir qué falta y en qué archivo\n\n## Prohibido\n- Modificar archivos de codigo\n- Aprobar implementaciones incompletas\n`,
1215
+ 'structure.md': `# Estructura — ${proj}\n\n## Directorios aprobados\n- . (raiz del proyecto)\n\n## Prohibidos\n- node_modules/, dist/, .git/, build/, target/, __pycache__/, .venv/\n`,
1216
+ 'patterns.md': `# Patrones — ${proj}\n\n## Nombrado\n- Archivos: kebab-case\n- Clases: PascalCase\n- Variables/funciones: camelCase\n- Constantes: UPPER_SNAKE_CASE\n\n## Reglas generales\n- Sin console.log de debug en produccion\n- Un archivo = una responsabilidad principal\n`,
1217
+ };
1218
+ for (const [name, content] of Object.entries(rulesSeeds)) {
1219
+ const fp = path.join(rulesDir, name);
1220
+ try {
1221
+ await fs.access(fp);
1222
+ }
1223
+ catch {
1224
+ await writeFile(fp, content);
1225
+ } // only create if missing
1226
+ }
1227
+ // NOTE: cleanup of stray files moved to AFTER successful parse, to avoid
1228
+ // wiping previous docs when the explorer fails (e.g. tool-call hallucination).
988
1229
  // Build a quick filesystem snapshot to give the explorer context
989
1230
  let fsSnapshot = '';
990
1231
  try {
@@ -992,6 +1233,17 @@ INSTRUCCIONES:
992
1233
  fsSnapshot = execSync(`find "${this.projectDir}" -maxdepth 3 -not -path '*/.agent/*' -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/__pycache__/*' -not -path '*/.venv/*' -not -path '*/target/*' -not -path '*/build/*' -not -name '.git' | sort | head -300`, { encoding: 'utf-8', timeout: 10000 });
993
1234
  }
994
1235
  catch { /* ignore */ }
1236
+ // Pre-read real file contents so the LLM has actual data instead of needing tools
1237
+ const sp0 = this._startSpinner('explorer pre-fetch reading project files');
1238
+ let prefetched = '';
1239
+ try {
1240
+ prefetched = await this._prefetchProjectFiles();
1241
+ sp0.push(`${prefetched.split('## COMPONENTE:').length - 1} componente(s) leidos`);
1242
+ }
1243
+ catch (err) {
1244
+ sp0.push(`prefetch error: ${err.message}`);
1245
+ }
1246
+ sp0.stop();
995
1247
  // Read existing main architecture doc
996
1248
  const mainArchPath = path.join(contextDir, 'architecture.md');
997
1249
  let existingMainArch = '';
@@ -1024,6 +1276,8 @@ STACK: ${this.config.stack}
1024
1276
  ESTRUCTURA DE ARCHIVOS (excluye .agent, node_modules, .git, dist, __pycache__, .venv, target, build):
1025
1277
  ${fsSnapshot}
1026
1278
 
1279
+ ${prefetched ? `================================================================\nARCHIVOS REALES LEIDOS DEL PROYECTO (esta es tu fuente de verdad)\n================================================================\n${prefetched}\n` : ''}
1280
+
1027
1281
  ${existingMainArch ? `=== DOC EXISTENTE: architecture.md (principal) ===\n${existingMainArch.slice(0, 2000)}\n` : ''}
1028
1282
  ${existingComponentDocs ? `=== DOC EXISTENTE por componente ===\n${existingComponentDocs.slice(0, 3000)}\n` : ''}
1029
1283
 
@@ -1031,36 +1285,49 @@ ${existingComponentDocs ? `=== DOC EXISTENTE por componente ===\n${existingCompo
1031
1285
  OBJETIVO
1032
1286
  ================================================================
1033
1287
  Generar documentacion ESCALONADA en 3 niveles, util TANTO para personas funcionales (PM, negocio) como tecnicas (devs).
1034
- Detallada pero NO excesiva. Concreta, con datos REALES leidos de los archivos. Cero especulaciones.
1288
+ Detallada pero NO excesiva. Concreta, con datos REALES extraidos de los archivos que estan mas arriba. Cero especulaciones.
1035
1289
 
1036
1290
  ================================================================
1037
1291
  REGLA DE ORO — PROHIBIDO INVENTAR
1038
1292
  ================================================================
1039
1293
  NO uses jamas: "Inferido", "Probablemente", "Posiblemente", "Asumido", "(supuesto)", "(quizas)", "parece ser".
1040
- Si no podes verificar un dato leyendo un archivo concreto OMITELO. Es mejor una doc corta y veraz que una larga llena de suposiciones.
1041
- Cada puerto, version, endpoint, env var, schema o ruta que aparezca en la doc DEBE haber sido leido de un archivo real.
1294
+ Si un dato (puerto, version, endpoint, env var, schema, ruta) no aparece en los ARCHIVOS REALES de arriba OMITILO.
1295
+ Es mejor una doc corta y veraz que una larga llena de suposiciones.
1296
+ NO devuelvas marcadores tipo "TOOL_CALL", "tool_use", "function_call" — vos NO tenes que llamar herramientas, tenes que devolver MARKDOWN.
1042
1297
 
1043
1298
  ================================================================
1044
- WORKFLOW QUE TENES QUE EJECUTAR (usa tus tools de read_file/grep_search/list_directory)
1299
+ COMO TRABAJAR (lectura ya hecha por el engine)
1045
1300
  ================================================================
1046
- 1. Lista DIRECTORIO_TRABAJO. Identifica los componentes (apps/services/libs). Descarta lo trivial (solo configs/docs/scripts).
1047
- 2. Para CADA componente no trivial, LEE COMO MINIMO:
1048
- a. Manifest del proyecto: package.json | requirements.txt | pyproject.toml | pom.xml | build.gradle | Cargo.toml | go.mod
1049
- de ahi sacas: nombre, version, framework, dependencias clave con sus versiones, scripts/tasks, entry point
1050
- b. Entry point real: src/main.ts | index.js | app.py | Application.java | cmd/main.go (segun stack)
1051
- de ahi sacas: puerto, middlewares, modulos cargados, configuracion inicial
1052
- c. Archivo de variables de entorno: .env | .env.example | application.yml | config.* | settings.py
1053
- de ahi sacas: variables reales con su proposito
1054
- d. Al menos 2-3 controladores/routers/handlers principales
1055
- de ahi extraes endpoints REALES (metodo, ruta, parametros)
1056
- e. Al menos 2-3 modelos/schemas/entities principales
1057
- → de ahi extraes el shape de datos
1058
- f. README.md del componente si existe
1059
- 3. Mapea RELACIONES REALES entre componentes (no asumas):
1060
- - Buscando URLs/hosts de otros servicios en .env
1061
- - Buscando imports cross-component
1062
- - Buscando cadenas de conexion a BBDD/colas/cache
1063
- 4. Recien con esa informacion concreta, genera los archivos de documentacion.
1301
+ El engine YA leyo por vos los archivos clave de cada componente y te los pasa arriba en la seccion "ARCHIVOS REALES LEIDOS DEL PROYECTO". Tu tarea es:
1302
+
1303
+ 1. Identificar los componentes en la estructura de archivos.
1304
+ 2. Para cada componente, extraer de sus archivos reales:
1305
+ - Del MANIFEST: nombre, version, framework, dependencias clave con sus versiones, scripts, entry point
1306
+ - Del ENTRY POINT: puerto real, middlewares, modulos cargados
1307
+ - De los CONFIG/ENV: variables reales y su proposito
1308
+ - De los CONTROLLER/ROUTER: endpoints REALES (metodo, ruta, parametros, anotaciones como @GetMapping/@PostMapping/router.get)
1309
+ - De los SCHEMA/MODEL/DOMAIN: shape real de los datos (campos, tipos, anotaciones @Column/@Entity)
1310
+ 3. Identificar relaciones REALES entre componentes (URLs/hosts de otros servicios en .env, imports cross-component, conexiones a BBDD).
1311
+ 4. Generar la documentacion en los 3 niveles, en formato markdown, dentro de los bloques === ... ===.
1312
+
1313
+ REGLA CRITICA PARA MODULOS (NIVEL 2):
1314
+ - Los modulos SON LAS FEATURES DEL NEGOCIO, NO las capas tecnicas.
1315
+ - MAL: modulos llamados "web-api", "security", "data-access" — eso es hablar de capas, no de features
1316
+ - BIEN: modulos llamados "clients", "sales", "products", "auth", "orders" — eso es hablar de features reales
1317
+ - Para identificar los modulos, MIRA los CONTROLLER/ROUTER pre-leidos: el nombre del modulo = lo que maneja ese controller
1318
+ * ClientController o clients.controller modulo "clients"
1319
+ * SaleController o sale.routes → modulo "sales"
1320
+ * AuthController o auth.handler → modulo "auth"
1321
+ - "Archivos clave" en cada modulo DEBE listar los archivos FUENTE REALES que leiste, NO solo el manifest.
1322
+ Ejemplo correcto:
1323
+ | Archivo (ruta relativa al componente) | Rol |
1324
+ | controller/ClientController.java | Endpoints REST /clients |
1325
+ | domain/Client.java | Entidad mapeada a tabla CLIENTS |
1326
+ | repository/ClientRepository.java | Acceso a base de datos |
1327
+ | src/clients/clients.controller.ts | Endpoints /clients |
1328
+ | src/clients/client.schema.ts | Modelo de datos Client |
1329
+
1330
+ NO inventes datos. Si un componente no tiene una de esas piezas en los archivos leidos, simplemente omiti esa seccion en su documentacion.
1064
1331
 
1065
1332
  ================================================================
1066
1333
  LENGUAJE DUAL (regla critica)
@@ -1074,190 +1341,254 @@ ESTRUCTURA DE LOS 3 NIVELES
1074
1341
  ================================================================
1075
1342
 
1076
1343
  ────────────────────────────────────────────────────────────────
1077
- NIVEL 0 — architecture.md (raiz de .agent/context/)
1078
- Indice global. Objetivo: 50-150 lineas. Concreto.
1079
1344
  ────────────────────────────────────────────────────────────────
1080
- Secciones obligatorias:
1345
+ NIVEL 0 — architecture.md (raiz de .agent/context/)
1346
+ Objetivo: 80-150 lineas. Panorama global real, no generico.
1347
+ ────────────────────────────────────────────────────────────────
1081
1348
 
1082
- # ${this.config.project} — Arquitectura Global (NIVEL 0)
1349
+ # [Nombre del proyecto] — Arquitectura Global (NIVEL 0)
1083
1350
 
1084
- > Indice de servicios y relaciones. Lectura escalonada:
1351
+ > Lectura escalonada:
1085
1352
  > NIVEL 0: este archivo
1086
1353
  > NIVEL 1: .agent/context/[componente]/architecture.md
1087
1354
  > NIVEL 2: .agent/context/[componente]/modules/[modulo].md
1088
1355
 
1089
1356
  ## 1. Overview funcional
1090
- 2-4 lineas en lenguaje simple: que es el sistema, para quien sirve, que problema resuelve.
1357
+ 2-4 lineas. Que es el sistema, para quien sirve, que problema resuelve.
1358
+ Si hay multiples componentes con roles distintos, nombrarlos y contrastarlos en el overview:
1359
+ ej. "X es la API de ingesta (escritura, batch), Y es la API de gestion (CRUD, portal Nexus)."
1091
1360
 
1092
1361
  ## 2. Componentes del proyecto
1093
- | Componente | Tipo | Stack + version | Puerto | Proposito (negocio) | Entry point |
1094
- |------------|------|-----------------|--------|---------------------|-------------|
1095
- (una fila por componente, con datos REALES)
1362
+ | Componente | Stack (version exacta) | Puerto | Rol principal | Consumers tipicos |
1363
+ |---|---|---|---|---|
1364
+ (datos REALES del manifest. "Consumers tipicos" = quien llama a este componente: frontend, proceso batch, sistema externo)
1096
1365
 
1097
1366
  ## 3. Relaciones entre componentes
1098
- | Origen | Destino | Tipo (HTTP / Import / DB / Queue / CORS) | Proposito |
1099
- |--------|---------|------------------------------------------|-----------|
1100
- (una fila por cada relacion confirmada)
1367
+ | Origen | Destino | Tipo | Proposito |
1368
+ |---|---|---|---|
1369
+ (incluir URLs/hosts reales si aparecen en .env o config: ej. "http://57.151.96.13:8000/v1/validate")
1101
1370
 
1102
1371
  ## 4. Diagrama de arquitectura
1103
- \`\`\`
1104
- [Frontend Web] ──HTTP──> [API Auth :8081]
1105
- │ │
1106
- │ v
1107
- └──HTTP──> [API Core :8080] ──> [MongoDB]
1372
+ Usar box-drawing (┌─┐│└┘┬┴├┤) y flechas (→ ──> ▼ ▲). Mostrar puertos REALES.
1373
+ Incluir endpoints clave inline en los componentes cuando se conocen.
1374
+ \`\`\`text
1375
+ ┌─────────────────────┐ ┌──────────────────────┐
1376
+ componente-a :8083 │ │ componente-b :8084 │
1377
+ │ POST /api/sales │ │ GET /api/nexus/combos │
1378
+ │ POST /api/stock │────────>│ PUT /api/nexus/... │
1379
+ └──────────┬──────────┘ └──────────────────────┘
1380
+ │ │
1381
+ ▼ ▼
1382
+ ┌──────────────┐ ┌──────────────┐
1383
+ │ SQL Server │ │ Auth Service│
1384
+ │ :31434 │ │ :8000 │
1385
+ └──────────────┘ └──────────────┘
1108
1386
  \`\`\`
1109
1387
 
1110
- ## 5. Flujos end-to-end principales (3-5)
1111
- Mappings funcional → tecnico, paso a paso. Ejemplo:
1112
- - **"Un usuario crea un pedido"**: WebApp → POST /orders (API Core) → valida JWT contra API Auth guarda en MongoDBdevuelve 201 con el pedido.
1388
+ ## 5. Flujos end-to-end principales
1389
+ Concretos, con componentes, endpoints y acciones reales. 2-5 flujos.
1390
+ - **"Nombre del flujo":** Actor → POST /endpoint (componente-a) → valida JWT con auth-serviceescribe en SQL Server responde 201.
1113
1391
 
1114
- ## 6. Prerequisitos para levantar el proyecto
1115
- Versiones REALES leidas de los manifests (no rangos genericos).
1392
+ ## 6. Prerequisitos para levantar
1393
+ Solo lo que es real: acceso de red a hosts externos, herramientas requeridas. Sin generalidades.
1116
1394
 
1117
- ## 7. Comandos globales de desarrollo
1395
+ ## 7. Comandos de desarrollo
1118
1396
  | Comando | Descripcion | Directorio |
1119
-
1120
- ## 8. Decisiones de arquitectura globales (opcional, solo si hay)
1397
+ |---|---|---|
1398
+ (comandos reales del Makefile, run.sh, package.json scripts, mvnw, etc.)
1121
1399
 
1122
1400
  ────────────────────────────────────────────────────────────────
1123
1401
  NIVEL 1 — [componente]/architecture.md
1124
- Detalle del componente. Objetivo: 80-200 lineas. Crear UNO POR CADA componente NO trivial.
1402
+ Objetivo: 120-250 lineas. Un archivo por cada componente no trivial.
1125
1403
  ────────────────────────────────────────────────────────────────
1126
- Secciones obligatorias:
1127
1404
 
1128
1405
  # [componente] — Arquitectura (NIVEL 1)
1129
1406
 
1130
1407
  ## Que hace
1131
- 2-3 lineas en lenguaje simple, sin tecnicismos.
1408
+ Parrafo 1: describe el rol del componente en lenguaje de negocio + quien lo consume.
1409
+ Parrafo 2 (si hay componentes hermanos): contrasta con ellos. ej. "A diferencia de X que solo escribe en bulk, esta API expone CRUD con paginacion y filtros para el portal."
1132
1410
 
1133
1411
  ## Casos de uso principales
1134
- 3-6 bullets en formato negocio:
1135
- - "Permite al usuario X..."
1136
- - "El sistema usa este servicio para Y..."
1412
+ Tabla con: Caso de uso | Actor (quien lo dispara) | Descripcion + endpoint si se conoce.
1413
+ | Caso de uso | Actor | Descripcion |
1414
+ |---|---|---|
1415
+ | Ingesta de ventas | Proceso batch BAT | POST /api/sales — upsert bulk de ventas finales |
1416
+ | Consulta de productos | Portal Nexus | GET /api/products?eanCode=XXX |
1137
1417
 
1138
1418
  ## Stack tecnico
1139
1419
  | Item | Valor |
1140
- |------|-------|
1141
- | Lenguaje | TypeScript 5.3 |
1142
- | Framework | NestJS 11.0.1 |
1143
- | ORM | Mongoose 9.3.3 |
1144
- | Auth | Passport JWT |
1145
- (datos reales del manifest)
1420
+ |---|---|
1421
+ | Lenguaje | Java 21 (Temurin LTS) |
1422
+ | Framework | Spring Boot 3.5.6 |
1423
+ | ORM | Spring Data JPA + Hibernate |
1424
+ | Seguridad | Spring Security 6 + JJWT |
1425
+ (versiones REALES del manifest. Si hay librerias clave como POI, MapStruct, HikariCP: incluirlas)
1146
1426
 
1147
1427
  ## Puerto y URLs
1148
- - **Puerto:** 8085 (de PORT en .env)
1149
- - **Swagger / docs:** http://localhost:8085/api/docs (si aplica)
1150
- - **Health check:** GET /health (si existe)
1151
-
1152
- ## Estructura interna (arbol real)
1428
+ | Recurso | URL |
1429
+ |---|---|
1430
+ | API base | http://localhost:8084 |
1431
+ | Swagger / docs | http://localhost:8084/swagger-ui/index.html |
1432
+ | Perfil activo | development (application-development.properties) |
1433
+
1434
+ ## Estructura de capas / paquetes
1435
+ Arbol con los archivos y directorios REALES que leiste. Usar → para anotar inline que hace cada clase o directorio.
1436
+ \`\`\`text
1437
+ paquete.raiz/
1438
+ ├── controller/ → endpoints REST, delegan a service, responden ApiResponse<T>
1439
+ │ ├── ClientController → CRUD /api/clients
1440
+ │ ├── SaleController → POST /api/sales, /api/sales/interim-sales
1441
+ │ └── ExportController → GET /api/export/combos (genera .xlsx)
1442
+ ├── service/ → interfaces de logica de negocio
1443
+ │ └── impl/ → implementaciones (@Transactional aqui)
1444
+ ├── repository/ → Spring Data JPA Repositories
1445
+ ├── domain/ → Entidades JPA (@Entity)
1446
+ │ ├── Client, InterimClient → clientes finales y en staging
1447
+ │ ├── Sale, InterimSale → ventas finales y en staging
1448
+ │ └── compositekeys/ → @IdClass para PKs compuestas
1449
+ ├── dto/ → contratos de API (Request/Response DTOs)
1450
+ ├── mapper/ → MapStruct: Entity ↔ DTO
1451
+ ├── security/ → filtros JWT, validacion, handler 401
1452
+ └── configuration/ → CORS, Swagger, SecurityConfig
1153
1453
  \`\`\`
1154
- src/
1155
- ├── main.ts # entry point, configura CORS y swagger
1156
- ├── auth/ # autenticacion JWT
1157
- ├── leads/ # gestion de leads
1158
- └── ...
1454
+ (Adaptar a la estructura real del proyecto: puede ser src/, app/, pkg/, etc.)
1455
+
1456
+ ## Endpoints reales
1457
+ SOLO los que se leyeron en los CONTROLLER/ROUTER files. Incluir columna Auth si se conoce.
1458
+ | Metodo | Ruta | Auth | Funcion |
1459
+ |---|---|---|---|
1460
+ | GET | /api/clients | JWT | Lista clientes (paginado, filtrable) |
1461
+ | POST | /api/sales | JWT | Upsert bulk de ventas finales |
1462
+ | GET | /api/export/combos | JWT/Azure | Descarga Excel con combos |
1463
+ (querystring params relevantes en la ruta, ej. ?eanCode= , ?page=&size= )
1464
+
1465
+ ## Formato de respuesta (si hay wrapper estandar)
1466
+ Si el codigo muestra un wrapper comun para todas las respuestas, documentarlo:
1467
+ \`\`\`json
1468
+ {
1469
+ "status": 200,
1470
+ "message": "Operacion exitosa",
1471
+ "data": [...],
1472
+ "pagination": { "page": 1, "size": 15, "totalElements": 120, "totalPages": 8 }
1473
+ }
1159
1474
  \`\`\`
1475
+ Si no hay wrapper, omitir esta seccion.
1160
1476
 
1161
1477
  ## Modulos internos
1162
- | Modulo | Proposito (negocio) | Doc tecnica |
1163
- |--------|---------------------|-------------|
1164
- | auth | Login y validacion de tokens | modules/auth.md |
1165
- | leads | Listado y consulta de negocios | modules/leads.md |
1166
-
1167
- ## Endpoints principales
1168
- | Metodo | Ruta | Modulo | Que hace (lenguaje simple) |
1169
- |--------|------|--------|----------------------------|
1170
- | GET | /leads | leads | Lista negocios filtrables |
1171
- | GET | /leads/:id | leads | Detalle de un negocio |
1172
- (SOLO endpoints reales extraidos de los controllers)
1173
-
1174
- ## Variables de entorno
1175
- | Variable | Default | Proposito |
1176
- |----------|---------|-----------|
1177
- | PORT | 8085 | Puerto del servicio |
1178
- | MONGODB_URI | mongodb://... | Conexion principal |
1179
-
1180
- ## Integraciones con otros servicios / sistemas
1181
- | Servicio externo | Tipo (HTTP/DB/Queue) | Proposito |
1478
+ | Modulo | Proposito | Doc tecnica |
1479
+ |---|---|---|
1480
+ | seguridad | Autenticacion JWT/Azure, roles, filtro HTTP | modules/security.md |
1481
+ | acceso-datos | JPA, Specifications, pool, entidades | modules/data-access.md |
1482
+ | api-web | Controladores, DTOs, validacion, exportacion | modules/web-api.md |
1483
+ (Agrupar por responsabilidad tecnica real, no inventar modulos)
1484
+
1485
+ ## Variables de configuracion clave
1486
+ | Propiedad / Variable | Valor actual | Proposito |
1487
+ |---|---|---|
1488
+ | server.port | 8084 | Puerto de la API |
1489
+ | spring.datasource.url | jdbc:sqlserver://... | Conexion SQL Server |
1490
+ | auth.url | http://57.151.96.13:8000/v1/validate | Servicio externo de validacion JWT |
1491
+ (Valores REALES del archivo de config/env leido. Si no se leyo el archivo, omitir la tabla)
1182
1492
 
1183
1493
  ## Como levantar
1184
- - **Instalar deps:** \`npm install\`
1185
- - **Servicios necesarios:** MongoDB corriendo, security-api accesible
1186
- - **Comando para iniciar:** \`npm run start:dev\`
1187
-
1188
- ## Comandos disponibles
1189
- | Comando | Que hace |
1494
+ Comando principal primero. Luego paso a paso si existe.
1495
+ \`\`\`bash
1496
+ ./run.sh # compila y levanta todo en uno (si existe)
1497
+ \`\`\`
1498
+ O paso a paso:
1499
+ \`\`\`bash
1500
+ source ./scripts/env.sh
1501
+ npm install && npm run dev
1502
+ \`\`\`
1190
1503
 
1191
- ## Decisiones tecnicas relevantes
1192
- (solo si hay decisiones notables; si no, omitir esta seccion)
1504
+ ## Testing (si hay tests en el proyecto)
1505
+ Frameworks, comando para correr, cobertura minima si se menciona en el manifest o config.
1506
+ \`\`\`bash
1507
+ npm test # todos los tests
1508
+ npm test -- --watch # modo watch
1509
+ \`\`\`
1193
1510
 
1194
1511
  ────────────────────────────────────────────────────────────────
1195
1512
  NIVEL 2 — [componente]/modules/[modulo].md
1196
- Detalle de un modulo interno. Objetivo: 40-120 lineas. Crear UNO POR cada modulo significativo.
1513
+ Objetivo: 50-120 lineas. Un archivo por modulo significativo.
1197
1514
  ────────────────────────────────────────────────────────────────
1198
- Secciones obligatorias:
1199
1515
 
1200
- # Modulo: [nombre] — [componente]
1516
+ # Modulo: [Nombre] — [componente]
1201
1517
 
1202
1518
  ## Funcion (lenguaje simple)
1203
- 1-2 lineas: "Permite al usuario gestionar X" / "El sistema usa este modulo para Y".
1519
+ 1-2 lineas: "Protege todos los endpoints /api/**. Cada request debe llevar un token JWT valido."
1204
1520
 
1205
1521
  ## Funcion (tecnica)
1206
- 1-2 lineas: como esta implementado a alto nivel.
1207
-
1208
- ## Flujos / casos de uso (1-3)
1209
- Escenarios paso a paso. Ejemplo:
1210
- - **Crear un pedido:** Cliente POST /orders middleware valida JWT → service.create() → guarda en DB → responde 201
1211
-
1212
- ## Endpoints / interfaces publicas (si aplica)
1213
- | Metodo | Ruta | Body | Respuesta |
1214
- |--------|------|------|-----------|
1215
-
1216
- ## Modelos / schemas relevantes
1217
- \`\`\`typescript
1218
- // shape REAL extraido del archivo de schema/model
1219
- {
1220
- _id: ObjectId,
1221
- name: string,
1222
- createdAt: Date,
1223
- ...
1224
- }
1522
+ 1-2 lineas: como esta implementado. ej. "Filtro OncePerRequestFilter que extrae el JWT del header Authorization, lo valida contra el servicio externo auth.url, y si es valido establece el SecurityContext."
1523
+
1524
+ ## Flujo principal (diagrama ASCII)
1525
+ Para modulos de seguridad, acceso a datos, o cualquiera con logica de decision/secuencia:
1526
+ mostrar el flujo con ASCII art y nombres REALES de clases/metodos.
1527
+ \`\`\`text
1528
+ HTTP Request
1529
+
1530
+
1531
+ AuthFilter (OncePerRequestFilter)
1532
+
1533
+ ├── X-User-Source == "Azure"?
1534
+ │ ▼ SI
1535
+ │ AzureHeaderValidator → construye usuario virtual desde headers
1536
+
1537
+ └── NO
1538
+
1539
+ JwtValidator → POST http://auth-service/validate → carga usuario de BD
1540
+
1541
+
1542
+ SecurityContextHolder.setAuthentication(...)
1543
+
1544
+
1545
+ @PreAuthorize("hasAnyAuthority('ROLE_A','ROLE_B')") en el controller
1225
1546
  \`\`\`
1226
1547
 
1227
- ## Reglas de negocio
1228
- - Validaciones: ...
1229
- - Condiciones especiales: ...
1230
- - Excepciones: ...
1548
+ ## Entidades / modelos relevantes (si aplica)
1549
+ Para modulos de acceso a datos: tabla de entidades con su proposito y notas.
1550
+ | Entidad | Proposito | Notas |
1551
+ |---|---|---|
1552
+ | Client | Clientes finales | Clave compuesta: ClientId |
1553
+ | InterimClient | Clientes en staging | Tabla intermedia antes de procesar |
1554
+
1555
+ ## Reglas del modulo
1556
+ - Rutas protegidas: /api/** → autenticacion obligatoria
1557
+ - Rutas publicas: OPTIONS /**, /swagger-ui/**, /*
1558
+ - Sin sesion: STATELESS — sin cookies
1559
+ - ddl-auto=validate → el schema nunca se auto-modifica
1231
1560
 
1232
1561
  ## Archivos clave
1562
+ Rutas RELATIVAS al directorio raiz del componente (no rutas absolutas, no solo el manifest).
1233
1563
  | Archivo | Rol |
1234
- |---------|-----|
1235
- | src/leads/leads.controller.ts | Endpoints /leads |
1236
- | src/leads/leads.service.ts | Logica de negocio |
1564
+ |---|---|
1565
+ | security/AuthFilter.java | Filtro principal — intercepta y valida cada request |
1566
+ | security/JwtValidator.java | Extrae username del JWT, verifica firma y expiracion |
1567
+ | configuration/SecurityConfig.java | Define SecurityFilterChain, rutas publicas/protegidas |
1568
+ | application-development.properties | auth.url, security.token.secret, security.token.expiration |
1237
1569
 
1238
1570
  ## Dependencias
1239
- - **Internas (mismo componente):** auth, common
1240
- - **Externas:** mongoose, class-validator
1241
- - **Cross-component:** llama a security-api via HTTP para validar JWT
1571
+ - Librerias clave con version si se conoce: io.jsonwebtoken:jjwt:0.9.1, spring-boot-starter-security
1572
+ - Cross-component si corresponde: "llama a http://auth-service:8000/v1/validate para validar tokens"
1242
1573
 
1243
1574
  ================================================================
1244
1575
  CALIBRACION DE DETALLE
1245
1576
  ================================================================
1246
- - NIVEL 0: 50-150 lineas (no menos, no mas)
1247
- - NIVEL 1: 80-200 lineas por componente
1248
- - NIVEL 2: 40-120 lineas por modulo
1249
- - Si te queda corto → leiste pocos archivos, lee mas
1250
- - Si te queda largo → estas repitiendo o agregando relleno, recorta
1577
+ - NIVEL 0: 80-150 lineas
1578
+ - NIVEL 1: 120-250 lineas por componente
1579
+ - NIVEL 2: 50-120 lineas por modulo
1580
+ - Si te queda corto → incluir mas endpoints reales, mas clases en el arbol, mas config values
1581
+ - Si te queda largo → estas repitiendo entre niveles o agregando relleno; recorta
1251
1582
 
1252
1583
  ================================================================
1253
1584
  QUE NO HACER
1254
1585
  ================================================================
1255
1586
  - NO usar "Inferido", "Probablemente", "(asumido)", "(quizas)", "parece"
1256
1587
  - NO repetir lo mismo entre niveles sin agregar valor (cada nivel zoomea mas)
1257
- - NO dejar tablas vacias ni con placeholders tipo "..."
1258
- - NO mezclar lenguaje tecnico puro: SIEMPRE empezar funcional, despues tecnico
1259
- - NO documentar componentes triviales (scripts, docs, configs sueltas) con su propia carpeta — mencionalos en NIVEL 0 y listo
1260
- - NO inventar endpoints, env vars, puertos o schemas que no leiste
1588
+ - NO dejar tablas vacias ni con placeholders tipo "..." o "ver manifest"
1589
+ - NO escribir overview que podria aplicar a cualquier proyecto (tiene que ser especifico de ESTE proyecto)
1590
+ - NO documentar componentes triviales (scripts sueltos, configs simples) con carpeta propia — mencionalos en NIVEL 0 y listo
1591
+ - NO inventar endpoints, env vars, puertos, hosts, clases o schemas que no leiste en los archivos reales
1261
1592
 
1262
1593
  ================================================================
1263
1594
  FORMATO DE SALIDA — OBLIGATORIO
@@ -1265,41 +1596,49 @@ FORMATO DE SALIDA — OBLIGATORIO
1265
1596
  Devolve UNICAMENTE bloques de archivos separados por marcadores ===. Nada de explicaciones extra fuera de los bloques.
1266
1597
 
1267
1598
  IMPORTANTE: TODAS las rutas son RELATIVAS al directorio del proyecto.
1268
- Los archivos se escriben SIEMPRE dentro de .agent/context/
1269
1599
 
1270
- Ejemplos de marcadores:
1600
+ Ejemplos de marcadores validos:
1271
1601
  === .agent/context/architecture.md ===
1272
1602
  === .agent/context/datamart-data-access-api/architecture.md ===
1273
1603
  === .agent/context/datamart-data-access-api/modules/auth.md ===
1274
1604
  === .agent/context/nexus-core-api/architecture.md ===
1275
- === .agent/context/nexus-core-api/modules/users.md ===
1605
+ === .agent/context/nexus-core-api/modules/clients.md ===
1276
1606
 
1277
1607
  === .agent/context/architecture.md ===
1278
- [contenido completo del NIVEL 0]
1279
-
1280
- === .agent/context/nombre-componente-1/architecture.md ===
1281
- [contenido completo del NIVEL 1 del componente 1]
1608
+ [contenido NIVEL 0]
1282
1609
 
1283
- === .agent/context/nombre-componente-1/modules/auth.md ===
1284
- [contenido completo del NIVEL 2 del modulo auth]
1610
+ === .agent/context/nombre-componente/architecture.md ===
1611
+ [contenido NIVEL 1]
1285
1612
 
1286
- === .agent/context/nombre-componente-1/modules/leads.md ===
1287
- [contenido completo del NIVEL 2 del modulo leads]
1288
-
1289
- === .agent/context/nombre-componente-2/architecture.md ===
1290
- ...
1613
+ === .agent/context/nombre-componente/modules/auth.md ===
1614
+ [contenido NIVEL 2]
1291
1615
 
1292
1616
  REGLAS DE PATHS:
1293
- - El archivo principal SIEMPRE es: === .agent/context/architecture.md ===
1617
+ - Arquitectura global: === .agent/context/architecture.md ===
1294
1618
  - Por componente: === .agent/context/[nombre-componente]/architecture.md ===
1295
1619
  - Por modulo interno: === .agent/context/[nombre-componente]/modules/[nombre-modulo].md ===
1296
1620
  - Documenta TODOS los componentes no triviales del DIRECTORIO_TRABAJO
1621
+ - NO escribir nada bajo .agent/docs/ — esa carpeta es de uso manual
1297
1622
  - Si existe documentacion previa, ACTUALIZALA preservando lo que sigue siendo valido y agregando lo nuevo`;
1298
1623
  const res = await this.runWithFallback('explorer', prompt, 'Exploracion');
1299
1624
  const text = extractCliText(res);
1625
+ // Always overwrite the single last-run report so we have a debug trail
1626
+ try {
1627
+ await writeFile(path.join(contextDir, 'explorer-last.md'), `# Explorer Report\n\nTask: ${effectiveTask}\nDate: ${new Date().toISOString()}\n\n${text}\n`);
1628
+ }
1629
+ catch { /* don't fail if save fails */ }
1630
+ // Detect tool-call hallucination or empty responses BEFORE touching anything
1631
+ if (looksLikeHallucinatedToolCalls(text)) {
1632
+ log.error('Explorer no devolvio bloques === *.md ===');
1633
+ log.warn('Posible causa: el LLM intento llamar tools que no estan disponibles via API.');
1634
+ log.warn(`Output guardado en: ${path.join(contextDir, 'explorer-last.md')}`);
1635
+ log.warn('Documentacion previa preservada (no se modifico nada).');
1636
+ return text;
1637
+ }
1300
1638
  // Parse sections separated by === path/file.md === markers
1301
1639
  const sections = text.split(/===\s+(.+?\.md)\s*===/).slice(1);
1302
- let filesWritten = 0;
1640
+ // First pass: collect all valid file writes WITHOUT touching disk yet (transactional)
1641
+ const pendingWrites = [];
1303
1642
  for (let i = 0; i < sections.length; i += 2) {
1304
1643
  const fileName = sections[i].trim();
1305
1644
  let content = sections[i + 1] ? sections[i + 1].trim() : '';
@@ -1307,141 +1646,57 @@ REGLAS DE PATHS:
1307
1646
  continue;
1308
1647
  // Clean up code fences if present
1309
1648
  content = content.replace(/^```markdown\s*/i, '').replace(/^```\s*$/gm, '').trim();
1310
- try {
1311
- let targetPath;
1312
- // Normalize: strip any .agent/context/ prefix from the marker
1313
- let relPath = fileName.replace(/^\.agent\/context\//i, '');
1314
- if (relPath === 'architecture.md' || relPath === 'ARCHITECTURE.md' || relPath === fileName) {
1315
- // Main architecture doc
1316
- if (relPath === 'architecture.md' || relPath === 'ARCHITECTURE.md') {
1317
- targetPath = mainArchPath;
1318
- }
1319
- else {
1320
- // Fallback for unrecognized paths: skip
1321
- continue;
1322
- }
1649
+ if (!content)
1650
+ continue;
1651
+ // All explorer output goes under .agent/context/ docs/ is manual-only
1652
+ const relPath = fileName.replace(/^\.agent\/context\//i, '').replace(/^\/+/, '');
1653
+ let targetPath = null;
1654
+ if (relPath === 'architecture.md' || relPath === 'ARCHITECTURE.md') {
1655
+ targetPath = mainArchPath;
1656
+ }
1657
+ else {
1658
+ const pathParts = relPath.split(/[\/\\]/).filter(Boolean);
1659
+ if (pathParts.length >= 3 && pathParts[pathParts.length - 2] === 'modules') {
1660
+ targetPath = path.join(contextDir, pathParts[pathParts.length - 3], 'modules', pathParts[pathParts.length - 1]);
1323
1661
  }
1324
- else {
1325
- // Component doc: component/architecture.md or component/modules/mod.md
1326
- const pathParts = relPath.split(/[\/\\]/).filter(Boolean);
1327
- if (pathParts.length >= 3 && pathParts[pathParts.length - 2] === 'modules') {
1328
- // component/modules/file.md
1329
- targetPath = path.join(contextDir, pathParts[pathParts.length - 3], 'modules', pathParts[pathParts.length - 1]);
1330
- }
1331
- else if (pathParts.length >= 2) {
1332
- // component/file.md
1333
- targetPath = path.join(contextDir, pathParts[pathParts.length - 2], pathParts[pathParts.length - 1]);
1334
- }
1335
- else {
1336
- // Fallback
1337
- continue;
1338
- }
1662
+ else if (pathParts.length >= 2) {
1663
+ targetPath = path.join(contextDir, pathParts[pathParts.length - 2], pathParts[pathParts.length - 1]);
1339
1664
  }
1665
+ }
1666
+ if (!targetPath)
1667
+ continue;
1668
+ pendingWrites.push({ targetPath, content, label: relPath });
1669
+ }
1670
+ if (pendingWrites.length === 0) {
1671
+ log.error('Explorer devolvio texto pero ningun bloque === *.md === fue parseable.');
1672
+ log.warn(`Output guardado en: ${path.join(contextDir, 'explorer-last.md')}`);
1673
+ log.warn('Documentacion previa preservada (no se modifico nada).');
1674
+ return text;
1675
+ }
1676
+ // Second pass: write everything to disk now that we know we have valid output
1677
+ let filesWritten = 0;
1678
+ for (const { targetPath, content, label } of pendingWrites) {
1679
+ try {
1340
1680
  await fs.mkdir(path.dirname(targetPath), { recursive: true });
1341
1681
  await writeFile(targetPath, content);
1342
1682
  filesWritten++;
1343
1683
  }
1344
1684
  catch (err) {
1345
- log.warn(`Failed to write ${fileName}: ${err.message}`);
1685
+ log.warn(`Failed to write ${label}: ${err.message}`);
1346
1686
  }
1347
1687
  }
1348
- if (filesWritten > 0) {
1349
- log.ok(`${filesWritten} documentation file(s) written`);
1350
- }
1351
- // Overwrite the single last-run report (no timestamp accumulation)
1688
+ log.ok(`${filesWritten} documentation file(s) written`);
1689
+ // NOW it's safe to clean stray root-level .md files (after successful write).
1352
1690
  try {
1353
- await writeFile(path.join(contextDir, 'explorer-last.md'), `# Explorer Report\n\nTask: ${effectiveTask}\nDate: ${new Date().toISOString()}\n\n${text}\n`);
1691
+ await this._cleanContextDir(contextDir);
1354
1692
  }
1355
- catch { /* don't fail if save fails */ }
1693
+ catch { /* don't fail run if cleanup fails */ }
1356
1694
  return text;
1357
1695
  }
1358
- /** Called when the current binary IS the configured explorer CLI (prevents recursion).
1359
- * Builds the full exploration prompt and calls Qwen API using own credentials. */
1696
+ /** Legacy entry point delegates to runExplorer.
1697
+ * Kept for backward compatibility with anything that may still call it. */
1360
1698
  async runExplorerDirect(task) {
1361
- const role = this.config.roles.explorer;
1362
- if (!role)
1363
- throw new Error('Explorer role not configured.');
1364
- const agentDir = path.join(this.projectDir, '.agent');
1365
- const contextDir = path.join(agentDir, 'context');
1366
- await fs.mkdir(contextDir, { recursive: true });
1367
- // Clean up stray files before running
1368
- await this._cleanContextDir(contextDir);
1369
- const archPath = path.join(contextDir, 'architecture.md');
1370
- let existingArch = '';
1371
- try {
1372
- existingArch = await readFile(archPath);
1373
- }
1374
- catch { /* new */ }
1375
- const effectiveTask = task || 'Explorar y documentar todas las aplicaciones y servicios del proyecto';
1376
- const context = await this.buildOrchestratorContext();
1377
- const prompt = this.buildRolePrompt('explorer', `TAREA DE EXPLORACION: ${effectiveTask}
1378
- DIRECTORIO_TRABAJO: ${this.projectDir}
1379
- PROYECTO: ${this.config.project}
1380
-
1381
- ${existingArch ? `DOCUMENTACION EXISTENTE:\n${existingArch.slice(0, 3000)}\n` : 'Sin documentacion previa.\n'}
1382
- CONTEXTO: ${context.slice(0, 2000)}
1383
-
1384
- OBJETIVO
1385
- Generar documentacion ESCALONADA en 3 niveles (global / componente / modulo), util para perfiles funcionales y tecnicos. Detallada pero concreta. Cero suposiciones.
1386
-
1387
- REGLA DE ORO — PROHIBIDO ESPECULAR
1388
- NO uses "Inferido", "Probablemente", "Asumido", "(quizas)", "parece". Si no podes verificar un dato leyendo un archivo, OMITELO. Cada puerto, version, endpoint, env var y schema debe haber sido leido de un archivo real.
1389
-
1390
- WORKFLOW (usa tus tools)
1391
- 1. Lista DIRECTORIO_TRABAJO y detecta los componentes no triviales.
1392
- 2. Para cada componente, LEE como minimo:
1393
- - Manifest (package.json | requirements.txt | pom.xml | build.gradle | go.mod | Cargo.toml)
1394
- - Entry point (main.ts | index.js | app.py | Application.java | cmd/main.go)
1395
- - .env / .env.example / application.yml / settings.py
1396
- - 2-3 controladores/routers principales (para endpoints reales)
1397
- - 2-3 schemas/models principales (para shapes reales)
1398
- 3. Mapea relaciones reales (URLs en .env, imports cross-component, conexiones a DB/colas).
1399
-
1400
- LENGUAJE DUAL
1401
- Cada seccion abre con UNA linea simple (que entienda un PM). Despues, el detalle tecnico.
1402
- - MAL: "El microservicio orquesta la persistencia mediante un repositorio."
1403
- - BIEN: "Guarda la informacion del usuario. Internamente usa un repositorio que abstrae MongoDB."
1404
-
1405
- NIVEL 0 — ${archPath} (50-150 lineas)
1406
- Secciones: Overview funcional · Tabla de componentes (con stack+version, puerto, proposito, entry point) · Tabla de relaciones (origen, destino, tipo, proposito) · Diagrama ASCII · 3-5 flujos end-to-end (mapping funcional → tecnico) · Prerequisitos · Comandos globales.
1407
-
1408
- NIVEL 1 — ${contextDir}/<componente>/architecture.md (80-200 lineas, uno por componente)
1409
- Secciones: Que hace (simple) · Casos de uso (3-6 bullets en formato negocio) · Stack tecnico (tabla con versiones reales) · Puerto y URLs · Estructura interna (arbol real) · Tabla de modulos · Tabla de endpoints reales · Tabla de variables de entorno · Integraciones · Como levantar · Comandos.
1410
-
1411
- NIVEL 2 — ${contextDir}/<componente>/modules/<modulo>.md (40-120 lineas, uno por modulo significativo)
1412
- Secciones: Funcion (simple) · Funcion (tecnica) · Flujos / casos de uso paso a paso · Endpoints / interfaces · Schemas reales en codigo · Reglas de negocio · Archivos clave · Dependencias internas/externas/cross-component.
1413
-
1414
- CALIBRACION
1415
- - NIVEL 0: 50-150 lineas. NIVEL 1: 80-200 por componente. NIVEL 2: 40-120 por modulo.
1416
- - Si te queda corto, leiste pocos archivos. Si te queda largo, recorta el relleno.
1417
- - Componentes triviales (solo scripts/docs): mencionalos en NIVEL 0 y NO les crees subcarpeta.
1418
-
1419
- REGLAS DE ESCRITURA
1420
- - Solo podes escribir archivos dentro de ${contextDir}/
1421
- - NO crees archivos sueltos en la raiz de ${contextDir}/ (excepto architecture.md)
1422
- - Por componente usa: ${contextDir}/<componente>/architecture.md
1423
- - Por modulo usa: ${contextDir}/<componente>/modules/<modulo>.md
1424
- - Si existe documentacion previa, ACTUALIZALA preservando lo valido`);
1425
- let result;
1426
- const sp = this._startSpinner(`agent-explorer ${role.model}`);
1427
- try {
1428
- result = await callQwenAPI(prompt, role.model, (c) => this._parseChunk(c).forEach(l => sp.push(l)));
1429
- sp.stop();
1430
- }
1431
- catch (err) {
1432
- sp.stop();
1433
- if (err.message?.startsWith('QWEN_AUTH_EXPIRED')) {
1434
- console.log(chalk.red('\n ✗ Sesión Qwen expirada.'));
1435
- console.log(chalk.yellow(' Ejecutá: agent-mp --login (o agent-explorer --login)\n'));
1436
- return '';
1437
- }
1438
- throw err;
1439
- }
1440
- try {
1441
- await writeFile(path.join(contextDir, 'explorer-last.md'), `# Explorer Report\n\nTask: ${effectiveTask}\nDate: ${new Date().toISOString()}\n\n${result}\n`);
1442
- }
1443
- catch { /* ignore */ }
1444
- return result;
1699
+ return this.runExplorer(task);
1445
1700
  }
1446
1701
  async runFullCycle(task) {
1447
1702
  // Header is now shown by the REPL before the first user message