chocolatito-code 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -0
- package/dist/agent/compaction.d.ts +65 -0
- package/dist/agent/compaction.js +134 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +35 -0
- package/dist/agent/loop.d.ts +91 -2
- package/dist/agent/loop.js +536 -78
- package/dist/agent/loopDetector.d.ts +57 -0
- package/dist/agent/loopDetector.js +0 -0
- package/dist/agent/mentions.d.ts +13 -0
- package/dist/agent/mentions.js +49 -0
- package/dist/agent/repoMap.d.ts +117 -0
- package/dist/agent/repoMap.js +560 -0
- package/dist/agent/result.d.ts +64 -0
- package/dist/agent/result.js +48 -0
- package/dist/agent/retry.d.ts +35 -0
- package/dist/agent/retry.js +123 -4
- package/dist/agent/subagent.js +62 -15
- package/dist/agent/syntaxValidator.js +288 -52
- package/dist/agent/toolGate.d.ts +58 -0
- package/dist/agent/toolGate.js +111 -0
- package/dist/agent/tracker.js +4 -7
- package/dist/agent/undoManager.d.ts +44 -11
- package/dist/agent/undoManager.js +95 -24
- package/dist/agent/usageMeter.d.ts +56 -0
- package/dist/agent/usageMeter.js +51 -0
- package/dist/agent/verifier.d.ts +49 -0
- package/dist/agent/verifier.js +158 -0
- package/dist/config/constants.js +14 -0
- package/dist/config/engine.d.ts +19 -0
- package/dist/config/engine.js +35 -3
- package/dist/config/license.d.ts +5 -0
- package/dist/config/license.js +107 -9
- package/dist/config/limits.d.ts +28 -0
- package/dist/config/limits.js +51 -0
- package/dist/config/permissions.d.ts +76 -1
- package/dist/config/permissions.js +379 -23
- package/dist/config/quota.js +24 -1
- package/dist/config/updater.d.ts +68 -0
- package/dist/config/updater.js +215 -0
- package/dist/hooks/manager.js +159 -14
- package/dist/index.js +222 -34
- package/dist/mcp/client.d.ts +22 -0
- package/dist/mcp/client.js +93 -11
- package/dist/prompts/systemPrompt.js +12 -1
- package/dist/sessions/manager.d.ts +12 -0
- package/dist/sessions/manager.js +33 -5
- package/dist/sessions/resume.d.ts +36 -0
- package/dist/sessions/resume.js +43 -0
- package/dist/skills/manager.d.ts +1 -0
- package/dist/skills/manager.js +9 -0
- package/dist/tools/binary.d.ts +46 -0
- package/dist/tools/binary.js +100 -0
- package/dist/tools/browserCdp.js +13 -1
- package/dist/tools/browserExtension.d.ts +5 -0
- package/dist/tools/browserExtension.js +202 -18
- package/dist/tools/browserSession.d.ts +23 -0
- package/dist/tools/browserSession.js +28 -0
- package/dist/tools/computerUse.js +33 -1
- package/dist/tools/createImage.d.ts +11 -0
- package/dist/tools/createImage.js +13 -1
- package/dist/tools/definitions.js +9 -3
- package/dist/tools/editDiagnosis.d.ts +54 -0
- package/dist/tools/editDiagnosis.js +142 -0
- package/dist/tools/editFile.d.ts +1 -0
- package/dist/tools/editFile.js +118 -62
- package/dist/tools/findFiles.d.ts +21 -0
- package/dist/tools/findFiles.js +75 -6
- package/dist/tools/getSystemInfo.js +15 -7
- package/dist/tools/gitTools.d.ts +24 -0
- package/dist/tools/gitTools.js +87 -26
- package/dist/tools/moveCopyFile.d.ts +18 -1
- package/dist/tools/moveCopyFile.js +74 -4
- package/dist/tools/patchCascade.d.ts +57 -0
- package/dist/tools/patchCascade.js +338 -0
- package/dist/tools/runCommand.js +213 -25
- package/dist/tools/runner.js +57 -4
- package/dist/tools/toolDefsComputer.js +12 -2
- package/dist/tools/truncator.d.ts +27 -1
- package/dist/tools/truncator.js +117 -11
- package/dist/tools/viewFile.js +15 -11
- package/dist/tools/visionBridge.js +7 -0
- package/dist/tools/webSearch.d.ts +21 -0
- package/dist/tools/webSearch.js +125 -14
- package/dist/tools/writeFile.js +18 -1
- package/dist/ui/interrupt.d.ts +4 -1
- package/dist/ui/interrupt.js +56 -5
- package/dist/ui/keyboardGuard.d.ts +55 -0
- package/dist/ui/keyboardGuard.js +65 -0
- package/dist/ui/loopPrompt.d.ts +17 -0
- package/dist/ui/loopPrompt.js +59 -0
- package/dist/ui/permissionPrompt.js +84 -2
- package/dist/ui/prompt.d.ts +35 -0
- package/dist/ui/prompt.js +345 -125
- package/dist/ui/reasoningStream.d.ts +8 -0
- package/dist/ui/reasoningStream.js +99 -7
- package/extension/README.md +95 -74
- package/extension/background.js +598 -178
- package/extension/content.js +92 -9
- package/extension/manifest.json +2 -1
- package/extension/popup.js +28 -12
- package/package.json +1 -1
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
/** Palabras que no son referencias a nada aunque lo parezcan. */
|
|
5
|
+
const RESERVADAS = new Set([
|
|
6
|
+
"if", "else", "for", "while", "return", "const", "let", "var", "function", "class", "new",
|
|
7
|
+
"this", "typeof", "instanceof", "import", "export", "from", "default", "async", "await",
|
|
8
|
+
"try", "catch", "finally", "throw", "switch", "case", "break", "continue", "do", "in", "of",
|
|
9
|
+
"true", "false", "null", "undefined", "void", "delete", "yield", "static", "public", "private",
|
|
10
|
+
"protected", "readonly", "extends", "implements", "interface", "type", "enum", "namespace",
|
|
11
|
+
"def", "self", "None", "True", "False", "elif", "pass", "lambda", "with", "as", "not", "and",
|
|
12
|
+
"or", "is", "raise", "except", "global", "nonlocal", "assert", "del", "print", "func", "package",
|
|
13
|
+
"struct", "trait", "impl", "fn", "let", "mut", "pub", "use", "mod", "match", "string", "int",
|
|
14
|
+
"bool", "float", "number", "boolean", "any", "unknown", "never", "object", "Promise", "Array",
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Patrones de definicion por lenguaje.
|
|
18
|
+
*
|
|
19
|
+
* Se prefiere perder alguna definicion a inventarsela: un simbolo fantasma en
|
|
20
|
+
* el mapa manda al agente a buscar algo que no existe, que es peor que no
|
|
21
|
+
* mencionarlo.
|
|
22
|
+
*/
|
|
23
|
+
const DEFINICIONES = {
|
|
24
|
+
js: [
|
|
25
|
+
/^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/,
|
|
26
|
+
/^\s*(?:export\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/,
|
|
27
|
+
/^\s*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/,
|
|
28
|
+
/^\s*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*[=<]/,
|
|
29
|
+
/^\s*(?:export\s+)?(?:declare\s+)?enum\s+([A-Za-z_$][\w$]*)/,
|
|
30
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>/,
|
|
31
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*(?:async\s+)?function/,
|
|
32
|
+
// Metodo de clase: dos espacios o mas de sangria y parentesis detras.
|
|
33
|
+
/^\s{2,}(?:(?:public|private|protected|static|async|readonly|get|set)\s+)*([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*[:{]/,
|
|
34
|
+
],
|
|
35
|
+
py: [
|
|
36
|
+
/^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)/,
|
|
37
|
+
/^\s*class\s+([A-Za-z_][\w]*)/,
|
|
38
|
+
/^([A-Z][A-Z0-9_]*)\s*=/, // constantes de modulo
|
|
39
|
+
],
|
|
40
|
+
go: [
|
|
41
|
+
/^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)/,
|
|
42
|
+
/^\s*type\s+([A-Za-z_][\w]*)/,
|
|
43
|
+
],
|
|
44
|
+
rs: [
|
|
45
|
+
/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/,
|
|
46
|
+
/^\s*(?:pub(?:\([^)]*\))?\s+)?(?:struct|enum|trait|type)\s+([A-Za-z_][\w]*)/,
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
const EXTENSIONES = {
|
|
50
|
+
".ts": "js", ".tsx": "js", ".js": "js", ".jsx": "js", ".mjs": "js", ".cjs": "js",
|
|
51
|
+
".mts": "js", ".cts": "js",
|
|
52
|
+
".py": "py", ".pyi": "py",
|
|
53
|
+
".go": "go",
|
|
54
|
+
".rs": "rs",
|
|
55
|
+
};
|
|
56
|
+
export function lenguajeDe(ruta) {
|
|
57
|
+
return EXTENSIONES[path.extname(ruta).toLowerCase()] ?? null;
|
|
58
|
+
}
|
|
59
|
+
export function extraerSimbolos(ruta, contenido) {
|
|
60
|
+
const lenguaje = lenguajeDe(ruta);
|
|
61
|
+
if (!lenguaje)
|
|
62
|
+
return { definiciones: [], referencias: new Map() };
|
|
63
|
+
const definiciones = [];
|
|
64
|
+
const definidosAqui = new Set();
|
|
65
|
+
const lineas = contenido.split("\n");
|
|
66
|
+
for (let i = 0; i < lineas.length; i++) {
|
|
67
|
+
const linea = lineas[i];
|
|
68
|
+
if (linea.length > 500)
|
|
69
|
+
continue; // linea minificada: no dice nada
|
|
70
|
+
for (const patron of DEFINICIONES[lenguaje]) {
|
|
71
|
+
const m = patron.exec(linea);
|
|
72
|
+
if (m && m[1] && !RESERVADAS.has(m[1]) && m[1] !== "constructor") {
|
|
73
|
+
if (!definidosAqui.has(m[1])) {
|
|
74
|
+
definidosAqui.add(m[1]);
|
|
75
|
+
definiciones.push({ archivo: ruta, linea: i + 1, nombre: m[1], clase: "def" });
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const referencias = new Map();
|
|
82
|
+
for (const m of contenido.matchAll(/[A-Za-z_$][\w$]*/g)) {
|
|
83
|
+
const nombre = m[0];
|
|
84
|
+
if (nombre.length < 3)
|
|
85
|
+
continue;
|
|
86
|
+
if (RESERVADAS.has(nombre))
|
|
87
|
+
continue;
|
|
88
|
+
if (definidosAqui.has(nombre))
|
|
89
|
+
continue; // usarse a si mismo no es un voto
|
|
90
|
+
// `algo.push(...)` no es una referencia al simbolo `push` de nadie: es un
|
|
91
|
+
// acceso a propiedad. Tree-sitter lo distingue gratis; con expresiones
|
|
92
|
+
// regulares hace falta mirar el caracter de antes.
|
|
93
|
+
//
|
|
94
|
+
// Sin esto el mapa entero se descuadra, y se comprobo ejecutandolo:
|
|
95
|
+
// `push`, `flush`, `get` o `stop` aparecen en TODOS los archivos del
|
|
96
|
+
// proyecto, asi que el modulo que resulte definir un metodo con ese nombre
|
|
97
|
+
// recibe un voto de cada archivo del repositorio. `src/ui/reasoningStream.ts`
|
|
98
|
+
// salia el primero del ranking, por delante del bucle del agente, por
|
|
99
|
+
// definir `push` y `flush`.
|
|
100
|
+
const antes = contenido.slice(Math.max(0, m.index - 40), m.index).replace(/\s+$/, "");
|
|
101
|
+
if (antes.endsWith(".") && !antes.endsWith(".."))
|
|
102
|
+
continue;
|
|
103
|
+
referencias.set(nombre, (referencias.get(nombre) ?? 0) + 1);
|
|
104
|
+
}
|
|
105
|
+
return { definiciones, referencias };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Un archivo de pruebas es parte del repositorio, pero no es por donde se
|
|
109
|
+
* empieza a entender un proyecto: nombra a todo el mundo (por eso puntua alto
|
|
110
|
+
* en el grafo) y no lo llama nadie. Se deja en el mapa, pero al final.
|
|
111
|
+
*/
|
|
112
|
+
export function esArchivoDePruebas(ruta) {
|
|
113
|
+
const normalizado = ruta.replace(/\\/g, "/").toLowerCase();
|
|
114
|
+
return (/(^|\/)(tests?|__tests__|spec)\//.test(normalizado) ||
|
|
115
|
+
/\.(test|spec)\.[cm]?[jt]sx?$/.test(normalizado) ||
|
|
116
|
+
/(^|\/)test_[^/]*\.py$/.test(normalizado));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Que archivos e identificadores esta nombrando el usuario.
|
|
120
|
+
*
|
|
121
|
+
* Las tres reglas son de Aider y las tres importan:
|
|
122
|
+
* - la ruta completa cuenta siempre, normalizando "\" a "/" (en Windows es la
|
|
123
|
+
* diferencia entre funcionar y no funcionar);
|
|
124
|
+
* - el nombre suelto ("loop.ts") solo cuenta si lleva un separador o un punto
|
|
125
|
+
* y si es UNICO en el proyecto, para no confundir "run" o "make";
|
|
126
|
+
* - un identificador de 5 letras o mas que coincide con el nombre de un
|
|
127
|
+
* archivo lo trae al mapa: escribir "arregla el fileTracker" deberia sacar
|
|
128
|
+
* src/agent/fileTracker.ts sin que nadie escriba la ruta.
|
|
129
|
+
*/
|
|
130
|
+
export function extraerMenciones(texto, archivos) {
|
|
131
|
+
const archivosMencionados = new Set();
|
|
132
|
+
const identificadores = new Set();
|
|
133
|
+
const palabras = new Set(texto
|
|
134
|
+
.split(/\s+/)
|
|
135
|
+
.map((p) => p.replace(/[,.!;:?]+$/, "").replace(/^["'`*_]+|["'`*_]+$/g, ""))
|
|
136
|
+
.map((p) => p.replace(/\\/g, "/"))
|
|
137
|
+
.filter(Boolean));
|
|
138
|
+
for (const m of texto.matchAll(/[A-Za-z_$][\w$]*/g)) {
|
|
139
|
+
if (!RESERVADAS.has(m[0]))
|
|
140
|
+
identificadores.add(m[0]);
|
|
141
|
+
}
|
|
142
|
+
// Cuantas veces aparece cada nombre base: si se repite, no se puede resolver.
|
|
143
|
+
const porNombre = new Map();
|
|
144
|
+
for (const a of archivos) {
|
|
145
|
+
const base = path.basename(a);
|
|
146
|
+
const lista = porNombre.get(base);
|
|
147
|
+
if (lista)
|
|
148
|
+
lista.push(a);
|
|
149
|
+
else
|
|
150
|
+
porNombre.set(base, [a]);
|
|
151
|
+
}
|
|
152
|
+
for (const a of archivos) {
|
|
153
|
+
const normalizado = a.replace(/\\/g, "/");
|
|
154
|
+
if (palabras.has(normalizado)) {
|
|
155
|
+
archivosMencionados.add(a);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const base = path.basename(a);
|
|
159
|
+
if (palabras.has(base) &&
|
|
160
|
+
/[/\\._-]/.test(base) &&
|
|
161
|
+
(porNombre.get(base)?.length ?? 0) === 1) {
|
|
162
|
+
archivosMencionados.add(a);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Puente identificador -> archivo, por el nombre sin extension.
|
|
166
|
+
for (const ident of identificadores) {
|
|
167
|
+
if (ident.length < 5)
|
|
168
|
+
continue;
|
|
169
|
+
for (const a of archivos) {
|
|
170
|
+
const raiz = path.basename(a, path.extname(a));
|
|
171
|
+
if (raiz.length >= 5 && raiz.toLowerCase() === ident.toLowerCase()) {
|
|
172
|
+
archivosMencionados.add(a);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return { archivos: archivosMencionados, identificadores };
|
|
177
|
+
}
|
|
178
|
+
// ------------------------------------------------------------------ PageRank
|
|
179
|
+
/**
|
|
180
|
+
* PageRank por iteracion de potencia. No hace falta una libreria de grafos:
|
|
181
|
+
* son veinte lineas y evita una dependencia nativa mas.
|
|
182
|
+
*/
|
|
183
|
+
export function pageRank(nodos, aristas, personalizacion, amortiguacion = 0.85, vueltas = 30) {
|
|
184
|
+
const n = nodos.length;
|
|
185
|
+
if (n === 0)
|
|
186
|
+
return new Map();
|
|
187
|
+
let rango = new Map(nodos.map((x) => [x, 1 / n]));
|
|
188
|
+
// El vector de personalizacion es a donde salta el navegante aleatorio: los
|
|
189
|
+
// archivos que el usuario ha nombrado, en vez de uno cualquiera.
|
|
190
|
+
const totalPers = [...personalizacion.values()].reduce((a, b) => a + b, 0);
|
|
191
|
+
const salto = new Map(nodos.map((x) => [x, totalPers > 0 ? (personalizacion.get(x) ?? 0) / totalPers : 1 / n]));
|
|
192
|
+
const salientes = new Map();
|
|
193
|
+
for (const [origen, lista] of aristas) {
|
|
194
|
+
salientes.set(origen, lista.reduce((a, e) => a + e.peso, 0));
|
|
195
|
+
}
|
|
196
|
+
for (let v = 0; v < vueltas; v++) {
|
|
197
|
+
const siguiente = new Map(nodos.map((x) => [x, 0]));
|
|
198
|
+
let colgante = 0;
|
|
199
|
+
for (const nodo of nodos) {
|
|
200
|
+
const r = rango.get(nodo) ?? 0;
|
|
201
|
+
const lista = aristas.get(nodo);
|
|
202
|
+
const total = salientes.get(nodo) ?? 0;
|
|
203
|
+
if (!lista || total === 0) {
|
|
204
|
+
colgante += r; // sin salidas: su rango se reparte por el salto
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
for (const { destino, peso } of lista) {
|
|
208
|
+
siguiente.set(destino, (siguiente.get(destino) ?? 0) + (r * peso) / total);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const nodo of nodos) {
|
|
212
|
+
const base = (salto.get(nodo) ?? 0) * (1 - amortiguacion + amortiguacion * colgante);
|
|
213
|
+
siguiente.set(nodo, base + amortiguacion * (siguiente.get(nodo) ?? 0));
|
|
214
|
+
}
|
|
215
|
+
rango = siguiente;
|
|
216
|
+
}
|
|
217
|
+
return rango;
|
|
218
|
+
}
|
|
219
|
+
/** Archivos que van siempre delante, aunque nadie los referencie. */
|
|
220
|
+
const IMPORTANTES = [
|
|
221
|
+
"README.md", "package.json", "tsconfig.json", "Cargo.toml", "go.mod", "pyproject.toml",
|
|
222
|
+
"requirements.txt", "Dockerfile", "CHOCOLATITO.md", "CLAUDE.md",
|
|
223
|
+
];
|
|
224
|
+
/**
|
|
225
|
+
* Lista de archivos del proyecto. Con git es instantaneo y respeta .gitignore
|
|
226
|
+
* gratis; sin git se recorre a mano evitando lo de siempre.
|
|
227
|
+
*/
|
|
228
|
+
/**
|
|
229
|
+
* La lista de archivos se pide en cada turno (para las menciones) y en cada
|
|
230
|
+
* mapa. Recorrer el repositorio entero para eso es tiempo regalado, pero
|
|
231
|
+
* guardarla para siempre haria invisible cualquier archivo creado a mitad de
|
|
232
|
+
* sesion. Treinta segundos es el punto medio: ningun turno la recalcula dos
|
|
233
|
+
* veces, y un archivo nuevo aparece en el siguiente.
|
|
234
|
+
*/
|
|
235
|
+
const cacheDeListados = new Map();
|
|
236
|
+
const VIDA_DEL_LISTADO_MS = 30_000;
|
|
237
|
+
export function listarArchivosCacheado(cwd, maxArchivos = 3_000) {
|
|
238
|
+
const clave = path.resolve(cwd);
|
|
239
|
+
const guardado = cacheDeListados.get(clave);
|
|
240
|
+
if (guardado && guardado.caduca > Date.now())
|
|
241
|
+
return guardado.lista;
|
|
242
|
+
const lista = listarArchivos(clave, maxArchivos);
|
|
243
|
+
cacheDeListados.set(clave, { lista, caduca: Date.now() + VIDA_DEL_LISTADO_MS });
|
|
244
|
+
return lista;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Indice simbolo -> archivos que lo definen, cacheado como el listado.
|
|
248
|
+
*
|
|
249
|
+
* Sirve para lo que la gente hace de verdad: escribir "arregla
|
|
250
|
+
* calcularImpuesto" en vez de la ruta. El puente por nombre de archivo de
|
|
251
|
+
* extraerMenciones no llega ahi, porque el simbolo casi nunca se llama igual
|
|
252
|
+
* que su archivo.
|
|
253
|
+
*/
|
|
254
|
+
const cacheDeIndices = new Map();
|
|
255
|
+
export function indiceDeDefiniciones(cwd) {
|
|
256
|
+
const clave = path.resolve(cwd);
|
|
257
|
+
const guardado = cacheDeIndices.get(clave);
|
|
258
|
+
if (guardado && guardado.caduca > Date.now())
|
|
259
|
+
return guardado.indice;
|
|
260
|
+
const indice = new Map();
|
|
261
|
+
for (const rel of listarArchivosCacheado(clave)) {
|
|
262
|
+
if (!lenguajeDe(rel))
|
|
263
|
+
continue;
|
|
264
|
+
let contenido;
|
|
265
|
+
try {
|
|
266
|
+
const abs = path.join(clave, rel);
|
|
267
|
+
if (fs.statSync(abs).size > 400_000)
|
|
268
|
+
continue;
|
|
269
|
+
contenido = fs.readFileSync(abs, "utf-8");
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
for (const d of extraerSimbolos(rel, contenido).definiciones) {
|
|
275
|
+
const lista = indice.get(d.nombre);
|
|
276
|
+
if (lista)
|
|
277
|
+
lista.push(rel);
|
|
278
|
+
else
|
|
279
|
+
indice.set(d.nombre, [rel]);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
cacheDeIndices.set(clave, { indice, caduca: Date.now() + VIDA_DEL_LISTADO_MS });
|
|
283
|
+
return indice;
|
|
284
|
+
}
|
|
285
|
+
export function listarArchivos(cwd, maxArchivos = 3_000) {
|
|
286
|
+
try {
|
|
287
|
+
const salida = execFileSync("git", ["ls-files"], {
|
|
288
|
+
cwd,
|
|
289
|
+
encoding: "utf-8",
|
|
290
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
291
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
292
|
+
});
|
|
293
|
+
const lista = salida.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
294
|
+
if (lista.length > 0)
|
|
295
|
+
return lista.slice(0, maxArchivos);
|
|
296
|
+
}
|
|
297
|
+
catch { }
|
|
298
|
+
const IGNORAR = new Set(["node_modules", ".git", "dist", "build", "out", "target", "__pycache__", ".venv", "venv", "coverage"]);
|
|
299
|
+
const encontrados = [];
|
|
300
|
+
const recorrer = (dir, prefijo) => {
|
|
301
|
+
if (encontrados.length >= maxArchivos)
|
|
302
|
+
return;
|
|
303
|
+
let entradas;
|
|
304
|
+
try {
|
|
305
|
+
entradas = fs.readdirSync(dir, { withFileTypes: true });
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
for (const e of entradas) {
|
|
311
|
+
if (encontrados.length >= maxArchivos)
|
|
312
|
+
return;
|
|
313
|
+
if (e.name.startsWith(".") && e.name !== ".github")
|
|
314
|
+
continue;
|
|
315
|
+
const rel = prefijo ? `${prefijo}/${e.name}` : e.name;
|
|
316
|
+
if (e.isDirectory()) {
|
|
317
|
+
if (IGNORAR.has(e.name))
|
|
318
|
+
continue;
|
|
319
|
+
recorrer(path.join(dir, e.name), rel);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
encontrados.push(rel);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
recorrer(cwd, "");
|
|
327
|
+
return encontrados;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Construye el mapa. Devuelve el texto listo para inyectar, o "" si no hay
|
|
331
|
+
* nada que ensenar (proyecto sin codigo reconocible, o presupuesto cero).
|
|
332
|
+
*/
|
|
333
|
+
export function construirMapa(opciones) {
|
|
334
|
+
const { cwd, peticion = "", abiertos = [], presupuesto = 4_000 } = opciones;
|
|
335
|
+
if (presupuesto <= 0)
|
|
336
|
+
return "";
|
|
337
|
+
const todos = listarArchivos(cwd, opciones.maxArchivos ?? 3_000);
|
|
338
|
+
const menciones = extraerMenciones(peticion, todos);
|
|
339
|
+
// Los tests salen del grafo, no solo del ranking final.
|
|
340
|
+
//
|
|
341
|
+
// Se comprobo ejecutandolo: dejandolos dentro, `tests/input.test.mjs` era el
|
|
342
|
+
// archivo con mas rango de todo el proyecto. Un test define ayudantes con
|
|
343
|
+
// nombres corrientes (`read`, `tecla`, `dist`) y nombra a media base de
|
|
344
|
+
// codigo, asi que acumula votos por los dos lados sin ser una dependencia de
|
|
345
|
+
// nada. Si el usuario los nombra, vuelven a entrar.
|
|
346
|
+
const pidenTests = /\b(test|tests|prueba|pruebas|spec)\b/i.test(peticion) ||
|
|
347
|
+
[...menciones.archivos].some(esArchivoDePruebas);
|
|
348
|
+
const conCodigo = todos.filter((a) => lenguajeDe(a) !== null && (pidenTests || !esArchivoDePruebas(a)));
|
|
349
|
+
if (conCodigo.length === 0)
|
|
350
|
+
return "";
|
|
351
|
+
// Un archivo por encima de 400 KB no es codigo que nadie vaya a leer.
|
|
352
|
+
const defines = new Map(); // ident -> archivos que lo definen
|
|
353
|
+
const referencias = new Map(); // ident -> archivo -> veces
|
|
354
|
+
const definicionesPorArchivo = new Map();
|
|
355
|
+
for (const rel of conCodigo) {
|
|
356
|
+
const abs = path.join(cwd, rel);
|
|
357
|
+
let contenido;
|
|
358
|
+
try {
|
|
359
|
+
if (fs.statSync(abs).size > 400_000)
|
|
360
|
+
continue;
|
|
361
|
+
contenido = fs.readFileSync(abs, "utf-8");
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const simbolos = extraerSimbolos(rel, contenido);
|
|
367
|
+
if (simbolos.definiciones.length > 0)
|
|
368
|
+
definicionesPorArchivo.set(rel, simbolos.definiciones);
|
|
369
|
+
for (const d of simbolos.definiciones) {
|
|
370
|
+
let quienes = defines.get(d.nombre);
|
|
371
|
+
if (!quienes)
|
|
372
|
+
defines.set(d.nombre, (quienes = new Set()));
|
|
373
|
+
quienes.add(rel);
|
|
374
|
+
}
|
|
375
|
+
for (const [nombre, veces] of simbolos.referencias) {
|
|
376
|
+
let porArchivo = referencias.get(nombre);
|
|
377
|
+
if (!porArchivo)
|
|
378
|
+
referencias.set(nombre, (porArchivo = new Map()));
|
|
379
|
+
porArchivo.set(rel, (porArchivo.get(rel) ?? 0) + veces);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ---- grafo
|
|
383
|
+
const aristas = new Map();
|
|
384
|
+
/** destino -> identificador -> peso total que le llega. */
|
|
385
|
+
const votos = new Map();
|
|
386
|
+
const anadir = (origen, destino, peso, ident) => {
|
|
387
|
+
let lista = aristas.get(origen);
|
|
388
|
+
if (!lista)
|
|
389
|
+
aristas.set(origen, (lista = []));
|
|
390
|
+
lista.push({ destino, peso });
|
|
391
|
+
let porDef = votos.get(destino);
|
|
392
|
+
if (!porDef)
|
|
393
|
+
votos.set(destino, (porDef = new Map()));
|
|
394
|
+
porDef.set(ident, (porDef.get(ident) ?? 0) + peso);
|
|
395
|
+
};
|
|
396
|
+
const abiertosSet = new Set(abiertos.map((a) => a.replace(/\\/g, "/")));
|
|
397
|
+
for (const [ident, definidores] of defines) {
|
|
398
|
+
const usos = referencias.get(ident);
|
|
399
|
+
if (!usos)
|
|
400
|
+
continue;
|
|
401
|
+
for (const [usuario, veces] of usos) {
|
|
402
|
+
for (const definidor of definidores) {
|
|
403
|
+
if (definidor === usuario)
|
|
404
|
+
continue;
|
|
405
|
+
let mul = 1;
|
|
406
|
+
if (menciones.identificadores.has(ident))
|
|
407
|
+
mul *= 10; // lo nombro el usuario
|
|
408
|
+
if (/[_-]/.test(ident) || (/[a-z][A-Z]/.test(ident) && ident.length >= 8))
|
|
409
|
+
mul *= 10;
|
|
410
|
+
if (ident.startsWith("_"))
|
|
411
|
+
mul *= 0.1;
|
|
412
|
+
if (definidores.size > 5)
|
|
413
|
+
mul *= 0.1; // nombre demasiado comun
|
|
414
|
+
if (abiertosSet.has(usuario.replace(/\\/g, "/")))
|
|
415
|
+
mul *= 50;
|
|
416
|
+
// La raiz cuadrada evita que un identificador citado 200 veces aplaste
|
|
417
|
+
// a todos los demas.
|
|
418
|
+
anadir(usuario, definidor, mul * Math.sqrt(veces), ident);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const nodos = [...new Set([...definicionesPorArchivo.keys(), ...aristas.keys()])];
|
|
423
|
+
if (nodos.length === 0)
|
|
424
|
+
return "";
|
|
425
|
+
const personalizacion = new Map();
|
|
426
|
+
const enElGrafo = new Set(nodos);
|
|
427
|
+
for (const a of menciones.archivos)
|
|
428
|
+
if (enElGrafo.has(a))
|
|
429
|
+
personalizacion.set(a, 100 / nodos.length);
|
|
430
|
+
for (const a of abiertosSet)
|
|
431
|
+
if (enElGrafo.has(a))
|
|
432
|
+
personalizacion.set(a, 100 / nodos.length);
|
|
433
|
+
// Si el usuario nombra un simbolo, sube el archivo que lo DEFINE.
|
|
434
|
+
//
|
|
435
|
+
// El multiplicador x10 de las aristas solo funciona cuando alguien referencia
|
|
436
|
+
// ese simbolo: para una funcion nueva, o una que solo se usa en un sitio, no
|
|
437
|
+
// hay aristas que multiplicar y la mencion no servia de nada. Es justo el
|
|
438
|
+
// caso mas comun de una peticion ("cambia generarInforme").
|
|
439
|
+
for (const ident of menciones.identificadores) {
|
|
440
|
+
const definidores = defines.get(ident);
|
|
441
|
+
if (!definidores || definidores.size > 3)
|
|
442
|
+
continue; // un nombre comun no senala nada
|
|
443
|
+
for (const archivo of definidores) {
|
|
444
|
+
if (enElGrafo.has(archivo))
|
|
445
|
+
personalizacion.set(archivo, 100 / nodos.length);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// Sin pistas del usuario, PageRank contesta a "de quien depende todo el
|
|
449
|
+
// mundo", que son los cimientos compartidos. Es una respuesta util, pero deja
|
|
450
|
+
// fuera el punto de entrada y el orquestador, que no los llama casi nadie y
|
|
451
|
+
// son por donde se empieza a leer un proyecto. Se les da un empujon del
|
|
452
|
+
// tamano de una mencion.
|
|
453
|
+
if (personalizacion.size === 0) {
|
|
454
|
+
for (const nodo of nodos) {
|
|
455
|
+
const base = path.basename(nodo);
|
|
456
|
+
const enRaizDeFuentes = /^(src\/)?[^/]+$/.test(nodo.replace(/\\/g, "/"));
|
|
457
|
+
if (enRaizDeFuentes && /^(index|main|cli|app)\.[cm]?[jt]sx?$/.test(base)) {
|
|
458
|
+
personalizacion.set(nodo, 100 / nodos.length);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const rango = pageRank(nodos, aristas, personalizacion);
|
|
463
|
+
// ---- repartir el rango del archivo entre sus definiciones
|
|
464
|
+
const definiciones = [];
|
|
465
|
+
for (const [archivo, defs] of definicionesPorArchivo) {
|
|
466
|
+
const rangoArchivo = rango.get(archivo) ?? 0;
|
|
467
|
+
const recibidos = votos.get(archivo);
|
|
468
|
+
const totalVotos = recibidos ? [...recibidos.values()].reduce((a, b) => a + b, 0) : 0;
|
|
469
|
+
// Los tests nombran a todo el mundo y no los llama nadie: sin esto copan
|
|
470
|
+
// la cabeza del mapa y tapan el codigo que se quiere entender.
|
|
471
|
+
const castigoPruebas = esArchivoDePruebas(archivo) ? 0.05 : 1;
|
|
472
|
+
for (const d of defs) {
|
|
473
|
+
const voto = recibidos?.get(d.nombre) ?? 0;
|
|
474
|
+
// Una definicion que no usa nadie no vale cero: vale poco. Si valiera
|
|
475
|
+
// cero, un archivo nuevo no apareceria nunca en el mapa.
|
|
476
|
+
const parte = totalVotos > 0 ? voto / totalVotos : 0;
|
|
477
|
+
// Un nombre que se define en medio proyecto (push, flush, run, handler)
|
|
478
|
+
// no identifica nada: aparece arriba solo porque lo referencia todo el
|
|
479
|
+
// mundo, y es justo el que menos ayuda a orientarse.
|
|
480
|
+
const cuantosLoDefinen = defines.get(d.nombre)?.size ?? 1;
|
|
481
|
+
const castigoGenerico = cuantosLoDefinen > 3 ? 0.1 : cuantosLoDefinen > 1 ? 0.5 : 1;
|
|
482
|
+
// El usuario lo ha escrito con todas las letras. El x10 de las aristas no
|
|
483
|
+
// llega aqui: solo multiplica votos, y una funcion que no usa nadie
|
|
484
|
+
// todavia no tiene ninguno. Sin esto, pedir "cambia generarInforme" no
|
|
485
|
+
// subia generarInforme, que es lo unico que se pidio.
|
|
486
|
+
const empujonMencion = menciones.identificadores.has(d.nombre) ? 10 : 1;
|
|
487
|
+
definiciones.push({
|
|
488
|
+
archivo,
|
|
489
|
+
linea: d.linea,
|
|
490
|
+
nombre: d.nombre,
|
|
491
|
+
rango: rangoArchivo * (0.1 + parte) * castigoGenerico * castigoPruebas * empujonMencion,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
definiciones.sort((a, b) => b.rango - a.rango);
|
|
496
|
+
if (definiciones.length === 0)
|
|
497
|
+
return "";
|
|
498
|
+
// ---- presupuesto por biseccion
|
|
499
|
+
const importantes = IMPORTANTES.filter((f) => todos.includes(f));
|
|
500
|
+
const render = (cuantas) => renderizar(definiciones.slice(0, cuantas), importantes);
|
|
501
|
+
const tokens = (texto) => Math.ceil(texto.length / 4);
|
|
502
|
+
let bajo = 1;
|
|
503
|
+
let alto = definiciones.length;
|
|
504
|
+
let medio = Math.min(Math.floor(presupuesto / 25), definiciones.length) || 1;
|
|
505
|
+
let mejor = "";
|
|
506
|
+
while (bajo <= alto) {
|
|
507
|
+
const texto = render(medio);
|
|
508
|
+
const t = tokens(texto);
|
|
509
|
+
const error = Math.abs(t - presupuesto) / presupuesto;
|
|
510
|
+
if (t <= presupuesto && texto.length > mejor.length)
|
|
511
|
+
mejor = texto;
|
|
512
|
+
if (error < 0.15 && t <= presupuesto)
|
|
513
|
+
break;
|
|
514
|
+
if (t < presupuesto)
|
|
515
|
+
bajo = medio + 1;
|
|
516
|
+
else
|
|
517
|
+
alto = medio - 1;
|
|
518
|
+
medio = Math.floor((bajo + alto) / 2);
|
|
519
|
+
if (medio < 1)
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
return mejor || render(1);
|
|
523
|
+
}
|
|
524
|
+
function renderizar(definiciones, importantes) {
|
|
525
|
+
const porArchivo = new Map();
|
|
526
|
+
for (const d of definiciones) {
|
|
527
|
+
let lista = porArchivo.get(d.archivo);
|
|
528
|
+
if (!lista)
|
|
529
|
+
porArchivo.set(d.archivo, (lista = []));
|
|
530
|
+
lista.push(d);
|
|
531
|
+
}
|
|
532
|
+
const lineas = [];
|
|
533
|
+
if (importantes.length > 0) {
|
|
534
|
+
lineas.push(`Archivos clave: ${importantes.join(", ")}`);
|
|
535
|
+
lineas.push("");
|
|
536
|
+
}
|
|
537
|
+
for (const [archivo, defs] of porArchivo) {
|
|
538
|
+
lineas.push(`${archivo}:`);
|
|
539
|
+
const ordenadas = [...defs].sort((a, b) => a.linea - b.linea);
|
|
540
|
+
for (const d of ordenadas) {
|
|
541
|
+
lineas.push(` ${d.nombre} (L${d.linea})`);
|
|
542
|
+
}
|
|
543
|
+
lineas.push("");
|
|
544
|
+
}
|
|
545
|
+
return lineas.join("\n").trimEnd();
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* El bloque tal y como se le entrega al modelo.
|
|
549
|
+
*
|
|
550
|
+
* El encabezado no es decorativo: sin el, el modelo trata el mapa como codigo
|
|
551
|
+
* que acaba de leer y se pone a editar simbolos que solo ha visto de nombre.
|
|
552
|
+
*/
|
|
553
|
+
export function bloqueDeMapa(mapa) {
|
|
554
|
+
if (!mapa)
|
|
555
|
+
return "";
|
|
556
|
+
return (`[MAPA DEL REPOSITORIO]\n` +
|
|
557
|
+
`Indice de los simbolos mas relevantes del proyecto, ordenado por cuanto los usa el resto ` +
|
|
558
|
+
`del codigo. Son NOMBRES Y LINEAS, no el contenido: sirve para saber que existe y donde ` +
|
|
559
|
+
`mirar. Antes de editar cualquiera de estos archivos, leelo con view_file.\n\n${mapa}`);
|
|
560
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMO TERMINA UNA TAREA Y CON QUE CODIGO SE SALE
|
|
3
|
+
*
|
|
4
|
+
* El bucle sabia perfectamente por que paraba —cuota agotada, licencia caida,
|
|
5
|
+
* Esc, limite de iteraciones— pero no se lo contaba a nadie: runTask devolvia
|
|
6
|
+
* void y lo imprimia por pantalla. El modo no interactivo hacia process.exit(0)
|
|
7
|
+
* detras, asi que en CI un 401 o una cuota agotada se reportaban como exito y
|
|
8
|
+
* el pipeline seguia adelante creyendo que el agente habia hecho el trabajo.
|
|
9
|
+
*
|
|
10
|
+
* Aqui vive el vocabulario compartido: por que paro y que numero le corresponde.
|
|
11
|
+
* Va en su propio archivo para que index.ts pueda traducir motivo -> codigo sin
|
|
12
|
+
* arrastrar el bucle entero, y para que la tabla del README tenga una sola
|
|
13
|
+
* fuente de verdad.
|
|
14
|
+
*/
|
|
15
|
+
export type MotivoParada =
|
|
16
|
+
/** El modelo cerro su turno con una respuesta. Lo unico que es exito. */
|
|
17
|
+
"completado"
|
|
18
|
+
/** No habia margen de cuota (contador local) o el motor devolvio 402. */
|
|
19
|
+
| "cuota"
|
|
20
|
+
/** 401/403: licencia no valida para el motor, o clave de API mala. */
|
|
21
|
+
| "licencia"
|
|
22
|
+
/**
|
|
23
|
+
* 429 con los huecos de peticiones simultaneas ocupados.
|
|
24
|
+
*
|
|
25
|
+
* No es "error-motor": el motor esta bien y la licencia tambien. Es la unica
|
|
26
|
+
* parada que se arregla esperando, asi que merece codigo propio: un script
|
|
27
|
+
* puede dormir y reintentar en vez de dar la tarea por fallida.
|
|
28
|
+
*/
|
|
29
|
+
| "ocupado"
|
|
30
|
+
/** El usuario pulso Esc a mitad del turno. */
|
|
31
|
+
| "interrumpido"
|
|
32
|
+
/** El motor fallo de verdad y los reintentos no lo salvaron. */
|
|
33
|
+
| "error-motor"
|
|
34
|
+
/** Se acabaron las iteraciones sin que el modelo cerrara. */
|
|
35
|
+
| "limite-iteraciones";
|
|
36
|
+
export interface ResultadoTarea {
|
|
37
|
+
/** Solo true si el turno acabo por su propio pie. */
|
|
38
|
+
exito: boolean;
|
|
39
|
+
motivo: MotivoParada;
|
|
40
|
+
/** Texto ya legible del corte, si lo hay. Es lo que se le enseña al usuario. */
|
|
41
|
+
detalle?: string;
|
|
42
|
+
/** Iteraciones consumidas del bucle de herramientas. */
|
|
43
|
+
turnos: number;
|
|
44
|
+
tokensEntrada: number;
|
|
45
|
+
tokensSalida: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* CODIGOS DE SALIDA
|
|
49
|
+
*
|
|
50
|
+
* 0/1/2 son la convencion de toda la vida (exito, fallo, uso incorrecto). Cuota
|
|
51
|
+
* y licencia llevan codigo propio porque son las dos unicas paradas que NO se
|
|
52
|
+
* arreglan reintentando: un pipeline que las distinga puede avisar a quien paga
|
|
53
|
+
* en vez de volver a lanzar el trabajo. El 130 es 128+SIGINT, lo que ya espera
|
|
54
|
+
* cualquier script cuando se interrumpe algo.
|
|
55
|
+
*/
|
|
56
|
+
export declare const EXIT_OK = 0;
|
|
57
|
+
export declare const EXIT_FALLO = 1;
|
|
58
|
+
export declare const EXIT_USO = 2;
|
|
59
|
+
export declare const EXIT_CUOTA = 3;
|
|
60
|
+
export declare const EXIT_LICENCIA = 4;
|
|
61
|
+
/** Huecos simultaneos ocupados: lo unico que se arregla esperando y reintentando. */
|
|
62
|
+
export declare const EXIT_OCUPADO = 5;
|
|
63
|
+
export declare const EXIT_INTERRUMPIDO = 130;
|
|
64
|
+
export declare function codigoDeSalida(resultado: ResultadoTarea): number;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COMO TERMINA UNA TAREA Y CON QUE CODIGO SE SALE
|
|
3
|
+
*
|
|
4
|
+
* El bucle sabia perfectamente por que paraba —cuota agotada, licencia caida,
|
|
5
|
+
* Esc, limite de iteraciones— pero no se lo contaba a nadie: runTask devolvia
|
|
6
|
+
* void y lo imprimia por pantalla. El modo no interactivo hacia process.exit(0)
|
|
7
|
+
* detras, asi que en CI un 401 o una cuota agotada se reportaban como exito y
|
|
8
|
+
* el pipeline seguia adelante creyendo que el agente habia hecho el trabajo.
|
|
9
|
+
*
|
|
10
|
+
* Aqui vive el vocabulario compartido: por que paro y que numero le corresponde.
|
|
11
|
+
* Va en su propio archivo para que index.ts pueda traducir motivo -> codigo sin
|
|
12
|
+
* arrastrar el bucle entero, y para que la tabla del README tenga una sola
|
|
13
|
+
* fuente de verdad.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* CODIGOS DE SALIDA
|
|
17
|
+
*
|
|
18
|
+
* 0/1/2 son la convencion de toda la vida (exito, fallo, uso incorrecto). Cuota
|
|
19
|
+
* y licencia llevan codigo propio porque son las dos unicas paradas que NO se
|
|
20
|
+
* arreglan reintentando: un pipeline que las distinga puede avisar a quien paga
|
|
21
|
+
* en vez de volver a lanzar el trabajo. El 130 es 128+SIGINT, lo que ya espera
|
|
22
|
+
* cualquier script cuando se interrumpe algo.
|
|
23
|
+
*/
|
|
24
|
+
export const EXIT_OK = 0;
|
|
25
|
+
export const EXIT_FALLO = 1;
|
|
26
|
+
export const EXIT_USO = 2;
|
|
27
|
+
export const EXIT_CUOTA = 3;
|
|
28
|
+
export const EXIT_LICENCIA = 4;
|
|
29
|
+
/** Huecos simultaneos ocupados: lo unico que se arregla esperando y reintentando. */
|
|
30
|
+
export const EXIT_OCUPADO = 5;
|
|
31
|
+
export const EXIT_INTERRUMPIDO = 130;
|
|
32
|
+
export function codigoDeSalida(resultado) {
|
|
33
|
+
switch (resultado.motivo) {
|
|
34
|
+
case "completado":
|
|
35
|
+
return EXIT_OK;
|
|
36
|
+
case "cuota":
|
|
37
|
+
return EXIT_CUOTA;
|
|
38
|
+
case "licencia":
|
|
39
|
+
return EXIT_LICENCIA;
|
|
40
|
+
case "ocupado":
|
|
41
|
+
return EXIT_OCUPADO;
|
|
42
|
+
case "interrumpido":
|
|
43
|
+
return EXIT_INTERRUMPIDO;
|
|
44
|
+
case "error-motor":
|
|
45
|
+
case "limite-iteraciones":
|
|
46
|
+
return EXIT_FALLO;
|
|
47
|
+
}
|
|
48
|
+
}
|