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.
- package/dist/commands/repl.js +93 -144
- package/dist/commands/setup.js +64 -6
- package/dist/core/engine.d.ts +9 -2
- package/dist/core/engine.js +537 -282
- package/dist/index.js +26 -19
- package/dist/utils/qwen-auth.d.ts +14 -4
- package/dist/utils/qwen-auth.js +81 -8
- package/package.json +44 -1
package/dist/core/engine.js
CHANGED
|
@@ -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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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(
|
|
986
|
-
|
|
987
|
-
|
|
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
|
|
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
|
|
1041
|
-
|
|
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
|
-
|
|
1299
|
+
COMO TRABAJAR (lectura ya hecha por el engine)
|
|
1045
1300
|
================================================================
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
-
|
|
1345
|
+
NIVEL 0 — architecture.md (raiz de .agent/context/)
|
|
1346
|
+
Objetivo: 80-150 lineas. Panorama global real, no generico.
|
|
1347
|
+
────────────────────────────────────────────────────────────────
|
|
1081
1348
|
|
|
1082
|
-
#
|
|
1349
|
+
# [Nombre del proyecto] — Arquitectura Global (NIVEL 0)
|
|
1083
1350
|
|
|
1084
|
-
>
|
|
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
|
|
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 |
|
|
1094
|
-
|
|
1095
|
-
(
|
|
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
|
|
1099
|
-
|
|
1100
|
-
(
|
|
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
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
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
|
|
1111
|
-
|
|
1112
|
-
- **"
|
|
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-service → escribe en SQL Server → responde 201.
|
|
1113
1391
|
|
|
1114
|
-
## 6. Prerequisitos para levantar
|
|
1115
|
-
|
|
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
|
|
1395
|
+
## 7. Comandos de desarrollo
|
|
1118
1396
|
| Comando | Descripcion | Directorio |
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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 |
|
|
1142
|
-
| Framework |
|
|
1143
|
-
| ORM |
|
|
1144
|
-
|
|
|
1145
|
-
(
|
|
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
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
|
1163
|
-
|
|
1164
|
-
|
|
|
1165
|
-
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
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
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
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
|
-
##
|
|
1192
|
-
|
|
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
|
-
|
|
1513
|
+
Objetivo: 50-120 lineas. Un archivo por modulo significativo.
|
|
1197
1514
|
────────────────────────────────────────────────────────────────
|
|
1198
|
-
Secciones obligatorias:
|
|
1199
1515
|
|
|
1200
|
-
# Modulo: [
|
|
1516
|
+
# Modulo: [Nombre] — [componente]
|
|
1201
1517
|
|
|
1202
1518
|
## Funcion (lenguaje simple)
|
|
1203
|
-
1-2 lineas: "
|
|
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
|
|
1207
|
-
|
|
1208
|
-
##
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
-
##
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
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
|
-
|
|
|
1236
|
-
|
|
|
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
|
-
-
|
|
1240
|
-
-
|
|
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:
|
|
1247
|
-
- NIVEL 1:
|
|
1248
|
-
- NIVEL 2:
|
|
1249
|
-
- Si te queda corto →
|
|
1250
|
-
- Si te queda largo → estas repitiendo o agregando relleno
|
|
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
|
|
1259
|
-
- NO documentar componentes triviales (scripts
|
|
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/
|
|
1605
|
+
=== .agent/context/nexus-core-api/modules/clients.md ===
|
|
1276
1606
|
|
|
1277
1607
|
=== .agent/context/architecture.md ===
|
|
1278
|
-
[contenido
|
|
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
|
|
1284
|
-
[contenido
|
|
1610
|
+
=== .agent/context/nombre-componente/architecture.md ===
|
|
1611
|
+
[contenido NIVEL 1]
|
|
1285
1612
|
|
|
1286
|
-
=== .agent/context/nombre-componente
|
|
1287
|
-
[contenido
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
-
|
|
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 ${
|
|
1685
|
+
log.warn(`Failed to write ${label}: ${err.message}`);
|
|
1346
1686
|
}
|
|
1347
1687
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
|
1691
|
+
await this._cleanContextDir(contextDir);
|
|
1354
1692
|
}
|
|
1355
|
-
catch { /* don't fail if
|
|
1693
|
+
catch { /* don't fail run if cleanup fails */ }
|
|
1356
1694
|
return text;
|
|
1357
1695
|
}
|
|
1358
|
-
/**
|
|
1359
|
-
*
|
|
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
|
-
|
|
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
|