@slorenzot/mcp-azure 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,512 @@
1
+ # PRD: Corrección de Concurrencia en add_attachment
2
+
3
+ ## 📋 Resumen Ejecutivo
4
+
5
+ Corregir el problema de concurrencia en `ado_add_attachment` que causa el error **TF26071** cuando múltiples intentos de agregar adjuntos ocurren simultáneamente al mismo Work Item en Azure DevOps.
6
+
7
+ ## 🐛 Problema
8
+
9
+ ### Error Reportado
10
+ ```
11
+ TF26071: This work item has been changed by someone else since you opened it.
12
+ ```
13
+
14
+ ### Causa Raíz
15
+ Cuando múltiples llamadas a `ado_add_attachment` golpean el mismo `workItemId` en paralelo, Azure DevOps rechaza los cambios debido a conflicto de revisión. Cada `add_attachment` incrementa la revisión del Work Item.
16
+
17
+ ### Flujo del Problema
18
+ 1. **Usuario A** ejecuta `add_attachment` para WI #123 → Recupera WI versión 10
19
+ 2. **Usuario B** ejecuta `add_attachment` para WI #123 → Recupera WI versión 10
20
+ 3. **Usuario A** intenta actualizar WI #123 con adjunto → Azure DevOps acepta (versión 10→11)
21
+ 4. **Usuario B** intenta actualizar WI #123 con adjunto → **TF26071**: Versión 10 ya no es la actual
22
+ 5. **Resultado**: El segundo adjunto falla con error confuso para el usuario
23
+
24
+ ### Impacto
25
+ - ❌ **Pérdida de funcionalidad**: Los segundos intentos fallan
26
+ - ❌ **Experiencia de usuario**: Errores técnicos no comprensibles
27
+ - ❌ **Inconsistencia**: A veces funciona, a veces falla (race condition)
28
+ - ❌ **Sin workaround documentado**: Los usuarios no saben cómo manejarlo
29
+
30
+ ## ✅ Solución Propuesta
31
+
32
+ ### Estrategia Principal: Sistema de Cola por Work Item
33
+
34
+ Implementar una cola interna por Work Item que asegure que las operaciones de adjuntos sean **secuenciales** por WI, permitiendo paralelismo entre diferentes Work Items.
35
+
36
+ ### Arquitectura de la Solución
37
+
38
+ #### 1. Sistema de Cola por Work Item
39
+
40
+ ```typescript
41
+ interface AttachmentQueue {
42
+ // Mapa de colas por Work Item ID
43
+ queues: Map<number, Promise<AttachmentResult>>;
44
+
45
+ // Agregar operación a la cola del WI
46
+ addToQueue(workItemId: number, operation: () => Promise<AttachmentResult>): Promise<AttachmentResult>;
47
+
48
+ // Verificar si hay cola activa para el WI
49
+ hasActiveQueue(workItemId: number): boolean;
50
+ }
51
+ ```
52
+
53
+ #### 2. Retry Automático con Re-fetch
54
+
55
+ ```typescript
56
+ async function addAttachmentWithRetry(
57
+ workItemId: number,
58
+ attachmentData: AttachmentData,
59
+ maxRetries: number = 3
60
+ ): Promise<AttachmentResult> {
61
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
62
+ try {
63
+ // 1. Recuperar versión actual del WI (importante para cada intento)
64
+ const currentWI = await getWorkItem(workItemId);
65
+
66
+ // 2. Agregar adjunto usando la versión actual
67
+ return await addAttachment(workItemId, attachmentData);
68
+
69
+ } catch (error: any) {
70
+ // 3. Verificar si es error de concurrencia TF26071
71
+ if (isConcurrencyError(error) && attempt < maxRetries) {
72
+ // 4. Esperar backoff exponencial
73
+ await sleep(Math.pow(2, attempt) * 1000); // 2s, 4s, 8s
74
+
75
+ // 5. Re-fetch del WI para obtener la nueva versión
76
+ continue;
77
+ }
78
+
79
+ // 6. Si no es TF26071 o se agotaron reintentos, lanzar error
80
+ throw error;
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ #### 3. Integración en `ado_add_attachment`
87
+
88
+ ```typescript
89
+ server.tool(
90
+ "ado_add_attachment",
91
+ "Agrega un adjunto a un Work Item existente con manejo de concurrencia",
92
+ // ... parámetros existentes ...
93
+ async ({ workItemId, filePath, attachmentUrl, comment, name }) => {
94
+ validateConnection();
95
+ validateProject();
96
+
97
+ // Verificar si hay cola activa para este WI
98
+ if (attachmentQueue.hasActiveQueue(workItemId)) {
99
+ console.error(`⏳ Encolando adjunto para WI #${workItemId} (ya hay operación en curso)`);
100
+ return await attachmentQueue.addToQueue(workItemId, () =>
101
+ addAttachmentWithRetry(workItemId, { filePath, attachmentUrl, comment, name })
102
+ );
103
+ }
104
+
105
+ // No hay cola activa, ejecutar directamente con retry
106
+ console.error(`🚀 Ejecutando adjunto para WI #${workItemId}`);
107
+ return await addAttachmentWithRetry(workItemId, { filePath, attachmentUrl, comment, name });
108
+ }
109
+ );
110
+ ```
111
+
112
+ ## 🔧 Implementación Detallada
113
+
114
+ ### Paso 1: Funciones de Utilidad
115
+
116
+ ```typescript
117
+ // Detectar si el error es TF26071 (concurrencia)
118
+ function isConcurrencyError(error: any): boolean {
119
+ const errorString = error?.message || error?.toString() || '';
120
+ return errorString.includes('TF26071') ||
121
+ errorString.includes('changed by someone else');
122
+ }
123
+
124
+ // Función de sleep para backoff
125
+ function sleep(ms: number): Promise<void> {
126
+ return new Promise(resolve => setTimeout(resolve, ms));
127
+ }
128
+
129
+ // Obtener Work Item actualizado (para re-fetch en retry)
130
+ async function getWorkItem(workItemId: number): Promise<witInterfaces.WorkItem> {
131
+ const api = await getWitApi();
132
+ return await safeApiCall(
133
+ () => api.getWorkItem(workItemId, undefined, undefined, witInterfaces.WorkItemExpand.All),
134
+ `Error al obtener Work Item #${workItemId}`
135
+ );
136
+ }
137
+ ```
138
+
139
+ ### Paso 2: Implementación de Cola
140
+
141
+ ```typescript
142
+ // Sistema de cola por Work Item
143
+ class AttachmentQueueManager {
144
+ private queues: Map<number, Promise<any>> = new Map();
145
+
146
+ async addToQueue<T>(
147
+ workItemId: number,
148
+ operation: () => Promise<T>
149
+ ): Promise<T> {
150
+ // Si ya hay cola para este WI, esperar
151
+ const existingQueue = this.queues.get(workItemId);
152
+ if (existingQueue) {
153
+ console.error(`⏳ Esperando cola para WI #${workItemId}...`);
154
+ try {
155
+ await existingQueue;
156
+ } catch {
157
+ // Si la operación anterior falló, proceder con la nuestra
158
+ }
159
+ }
160
+
161
+ // Crear nueva cola para este WI
162
+ const queue = operation().finally(() => {
163
+ // Limpiar cola cuando termine
164
+ this.queues.delete(workItemId);
165
+ });
166
+
167
+ this.queues.set(workItemId, queue);
168
+ return queue;
169
+ }
170
+
171
+ hasActiveQueue(workItemId: number): boolean {
172
+ return this.queues.has(workItemId);
173
+ }
174
+ }
175
+
176
+ // Instancia global del gestor de colas
177
+ const attachmentQueue = new AttachmentQueueManager();
178
+ ```
179
+
180
+ ### Paso 3: Función Principal con Retry
181
+
182
+ ```typescript
183
+ // Función principal con retry y re-fetch
184
+ async function addAttachmentWithRetry(
185
+ api: witApi.IWorkItemTrackingApi,
186
+ workItemId: number,
187
+ attachmentData: {
188
+ filePath?: string;
189
+ attachmentUrl?: string;
190
+ comment?: string;
191
+ name?: string;
192
+ },
193
+ maxRetries: number = 3
194
+ ): Promise<{ content: any }> {
195
+ let lastError: any = null;
196
+
197
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
198
+ try {
199
+ console.error(`🔄 Intento ${attempt}/${maxRetries} para adjuntar a WI #${workItemId}`);
200
+
201
+ // 1. Recuperar versión actual del WI (CRÍTICO para cada intento)
202
+ const currentWI = await getWorkItem(workItemId);
203
+ console.error(`📄 WI #${workItemId} versión actual: ${currentWI.rev}`);
204
+
205
+ // 2. Procesar adjunto
206
+ let attachmentId: string | undefined;
207
+ let fileName: string | undefined;
208
+ let attachmentLinkUrl: string | undefined;
209
+
210
+ if (attachmentData.filePath) {
211
+ // Subir archivo nuevo
212
+ fileName = attachmentData.name || path.basename(attachmentData.filePath);
213
+ const attachment = await uploadAttachmentRest(attachmentData.filePath, fileName);
214
+ attachmentId = attachment.id;
215
+ attachmentLinkUrl = attachment.url;
216
+ } else if (attachmentData.attachmentUrl) {
217
+ // Usar adjunto existente
218
+ attachmentId = attachmentData.attachmentUrl.split('/attachments/')[1]?.split('?')[0];
219
+ fileName = attachmentData.name || "Archivo adjunto";
220
+ const baseUrl = currentOrg.endsWith("/") ? currentOrg.slice(0, -1) : currentOrg;
221
+ const encodedProject = getEncodedProject(currentProject);
222
+ attachmentLinkUrl = `${baseUrl}/${encodedProject}/_apis/wit/attachments/${attachmentId}`;
223
+ } else {
224
+ throw new Error("Debe proporcionar filePath o attachmentUrl");
225
+ }
226
+
227
+ // 3. Vincular adjunto al WI usando la versión ACTUAL
228
+ const patchDocument: VSSInterfaces.JsonPatchOperation[] = [
229
+ {
230
+ op: VSSInterfaces.Operation.Add,
231
+ path: "/relations/-",
232
+ value: {
233
+ rel: "AttachedFile",
234
+ url: attachmentLinkUrl,
235
+ attributes: {
236
+ name: fileName,
237
+ comment: attachmentData.comment || "",
238
+ },
239
+ },
240
+ },
241
+ ];
242
+
243
+ // 4. Actualizar WI con la versión actual
244
+ const updatedWI = await api.updateWorkItem(
245
+ null, // project
246
+ patchDocument,
247
+ workItemId
248
+ );
249
+
250
+ console.error(`✅ Adjunto agregado exitosamente a WI #${workItemId}`);
251
+
252
+ return {
253
+ content: [{
254
+ type: "text",
255
+ text: `Adjunto agregado exitosamente al Work Item #${workItemId}\n- Nombre: ${fileName}\n- URL: ${attachmentLinkUrl}`,
256
+ }],
257
+ };
258
+
259
+ } catch (error: any) {
260
+ lastError = error;
261
+ console.error(`❌ Error en intento ${attempt}:`, error.message);
262
+
263
+ // Verificar si es error de concurrencia y quedan reintentos
264
+ if (isConcurrencyError(error) && attempt < maxRetries) {
265
+ const backoffTime = Math.pow(2, attempt) * 1000;
266
+ console.error(`⏳ Error TF26071 detectado. Esperando ${backoffTime}ms antes de reintentar...`);
267
+ await sleep(backoffTime);
268
+ console.error(`🔄 Reintentando con nueva versión del WI...`);
269
+ continue;
270
+ }
271
+
272
+ // Si no es TF26071 o se agotaron reintentos, lanzar error
273
+ throw error;
274
+ }
275
+ }
276
+
277
+ // Si se agotaron todos los reintentos, lanzar el último error
278
+ throw lastError;
279
+ }
280
+ ```
281
+
282
+ ### Paso 4: Actualización de `ado_add_attachment`
283
+
284
+ ```typescript
285
+ server.tool(
286
+ "ado_add_attachment",
287
+ "Agrega un adjunto a un Work Item existente. Maneja automáticamente concurrencia cuando múltiples adjuntos se agregan al mismo WI.",
288
+ {
289
+ workItemId: z.number().describe("ID del Work Item"),
290
+ filePath: z.string().optional().describe("Ruta del archivo a subir (opcional si se usa attachmentUrl)"),
291
+ attachmentUrl: z.string().optional().describe("URL de un adjunto ya subido (opcional si se usa filePath)"),
292
+ comment: z.string().optional().describe("Comentario para el adjunto"),
293
+ name: z.string().optional().describe("Nombre del archivo (si no se especifica, usa el nombre del archivo original)"),
294
+ },
295
+ async ({ workItemId, filePath, attachmentUrl, comment, name }) => {
296
+ validateConnection();
297
+ validateProject();
298
+
299
+ const api = await getWitApi();
300
+
301
+ // VERIFICAR CONCURRENCIA: Usar sistema de cola
302
+ if (attachmentQueue.hasActiveQueue(workItemId)) {
303
+ console.error(`⏳ Encolando adjunto para WI #${workItemId} (ya hay operación en curso)`);
304
+ return await attachmentQueue.addToQueue(workItemId, () =>
305
+ addAttachmentWithRetry(api, workItemId, { filePath, attachmentUrl, comment, name })
306
+ );
307
+ }
308
+
309
+ // Sin concurrencia: ejecutar directamente
310
+ console.error(`🚀 Ejecutando adjunto para WI #${workItemId}`);
311
+ return await attachmentQueue.addToQueue(workItemId, () =>
312
+ addAttachmentWithRetry(api, workItemId, { filePath, attachmentUrl, comment, name })
313
+ );
314
+ }
315
+ );
316
+ ```
317
+
318
+ ## 📊 Casos de Uso
319
+
320
+ ### Caso 1: Adjunto Único (sin concurrencia)
321
+ ```
322
+ Usuario: add_attachment(123, "archivo.pdf")
323
+ Resultado: ✅ Adjunto agregado en primer intento
324
+ Logs:
325
+ 🚀 Ejecutando adjunto para WI #123
326
+ 📄 WI #123 versión actual: 10
327
+ ✅ Adjunto agregado exitosamente a WI #123
328
+ ```
329
+
330
+ ### Caso 2: Concurrencia (2 adjuntos simultáneos)
331
+ ```
332
+ Usuario A (hilo 1): add_attachment(123, "archivo1.pdf")
333
+ Usuario B (hilo 2): add_attachment(123, "archivo2.pdf")
334
+
335
+ Resultado:
336
+ Hilo 1: ✅ Adjunto agregado en primer intento
337
+ Hilo 2: ⏳ Encolado, espera a que termine hilo 1
338
+ Hilo 2: ✅ Adjunto agregado después de que hilo 1 terminó
339
+
340
+ Logs:
341
+ Hilo 1: 🚀 Ejecutando adjunto para WI #123
342
+ Hilo 2: ⏳ Encolando adjunto para WI #123 (ya hay operación en curso)
343
+ Hilo 1: 📄 WI #123 versión actual: 10
344
+ Hilo 1: ✅ Adjunto agregado exitosamente a WI #123
345
+ Hilo 2: 📄 WI #123 versión actual: 11
346
+ Hilo 2: ✅ Adjunto agregado exitosamente a WI #123
347
+ ```
348
+
349
+ ### Caso 3: Concurrencia con TF26071 (3 adjuntos simultáneos)
350
+ ```
351
+ Usuario A, B, C: add_attachment(123, "archivoX.pdf")
352
+
353
+ Resultado:
354
+ Hilo 1: ✅ Adjunto agregado (versión 10→11)
355
+ Hilo 2: ⏳ Encolado, espera hilo 1
356
+ Hilo 3: ⏳ Encolado, espera hilo 1
357
+ Hilo 2: 🔄 Intento 1 → TF26071 → Reintenta
358
+ Hilo 2: 📄 WI #123 versión actual: 11
359
+ Hilo 2: ✅ Adjunto agregado (versión 11→12)
360
+ Hilo 3: 🔄 Intento 1 → TF26071 → Reintenta
361
+ Hilo 3: 📄 WI #123 versión actual: 12
362
+ Hilo 3: ✅ Adjunto agregado (versión 12→13)
363
+ ```
364
+
365
+ ## ✅ Beneficios
366
+
367
+ ### Para Usuarios
368
+ - ✅ **Sin errores TF26071**: Manejo automático de concurrencia
369
+ - ✅ **Transparencia**: El sistema maneja los reintentos automáticamente
370
+ - ✅ **Predictibilidad**: Los adjuntos siempre se agregan, eventualmente
371
+ - ✅ **Mejor experiencia**: Sin necesidad de reintentar manualmente
372
+
373
+ ### Para Desarrolladores
374
+ - ✅ **Código limpio**: Lógica de concurrencia encapsulada
375
+ - ✅ **Reutilizable**: Sistema de cola puede usarse para otras operaciones
376
+ - ✅ **Debuggable**: Logs claros de lo que está pasando
377
+ - ✅ **Testable**: Lógica separada en funciones pequeñas
378
+
379
+ ## 🧪 Testing
380
+
381
+ ### Test Unitarios
382
+
383
+ ```typescript
384
+ // Test: Detección de error TF26071
385
+ describe('isConcurrencyError', () => {
386
+ it('debería detectar error TF26071', () => {
387
+ const error = new Error('TF26071: This work item has been changed by someone else since you opened it.');
388
+ expect(isConcurrencyError(error)).toBe(true);
389
+ });
390
+
391
+ it('no debería detectar otros errores', () => {
392
+ const error = new Error('Network error');
393
+ expect(isConcurrencyError(error)).toBe(false);
394
+ });
395
+ });
396
+
397
+ // Test: Sistema de cola
398
+ describe('AttachmentQueueManager', () => {
399
+ it('debería encolar operaciones concurrentes al mismo WI', async () => {
400
+ const queue = new AttachmentQueueManager();
401
+ const results: string[] = [];
402
+
403
+ // Simular 3 operaciones concurrentes al mismo WI
404
+ const op1 = queue.addToQueue(123, async () => { await sleep(100); return 'A'; });
405
+ const op2 = queue.addToQueue(123, async () => { await sleep(50); return 'B'; });
406
+ const op3 = queue.addToQueue(123, async () => { await sleep(50); return 'C'; });
407
+
408
+ results.push(await op1, await op2, await op3);
409
+ expect(results).toEqual(['A', 'B', 'C']);
410
+ });
411
+ });
412
+ ```
413
+
414
+ ### Test de Integración
415
+
416
+ ```typescript
417
+ // Test: Retry automático con concurrencia simulada
418
+ describe('addAttachmentWithRetry', () => {
419
+ it('debería reintentar automáticamente al recibir TF26071', async () => {
420
+ let callCount = 0;
421
+
422
+ // Mock de API que falla 2 veces con TF26071, luego tiene éxito
423
+ const mockApi = {
424
+ updateWorkItem: jest.fn().mockImplementation(async () => {
425
+ callCount++;
426
+ if (callCount <= 2) {
427
+ throw new Error('TF26071: This work item has been changed by someone else since you opened it.');
428
+ }
429
+ return { id: 123, rev: 10 + callCount };
430
+ })
431
+ } as any;
432
+
433
+ const result = await addAttachmentWithRetry(mockApi, 123, { filePath: 'test.pdf' });
434
+
435
+ expect(mockApi.updateWorkItem).toHaveBeenCalledTimes(3);
436
+ expect(callCount).toBe(3);
437
+ expect(result).toBeDefined();
438
+ });
439
+ });
440
+ ```
441
+
442
+ ## 🚀 Plan de Implementación
443
+
444
+ ### Fase 1: Implementación Core (Sprint 1)
445
+ - [ ] Funciones de utilidad: `isConcurrencyError`, `sleep`, `getWorkItem`
446
+ - [ ] Clase `AttachmentQueueManager`
447
+ - [ ] Función `addAttachmentWithRetry` con lógica de retry
448
+ - [ ] Tests unitarios para nuevas funciones
449
+
450
+ ### Fase 2: Integración (Sprint 1)
451
+ - [ ] Actualizar `ado_add_attachment` para usar sistema de cola
452
+ - [ ] Agregar logs informativos de concurrencia
453
+ - [ ] Tests de integración end-to-end
454
+
455
+ ### Fase 3: Documentación (Sprint 1)
456
+ - [ ] Actualizar README.md con manejo de concurrencia
457
+ - [ ] Agregar ejemplos de uso con concurrencia
458
+ - [ ] Documentar comportamientos esperados
459
+
460
+ ## 📝 Notas Técnicas
461
+
462
+ ### Consideraciones de Performance
463
+ - **Cola por WI**: Solo serializa operaciones del mismo WI, permite paralelismo entre diferentes WIs
464
+ - **Backoff exponencial**: Evita sobrecargar Azure DevOps con reintentos agresivos
465
+ - **Re-fetch eficiente**: Solo recupera datos necesarios del WI, no campos completos
466
+
467
+ ### Compatibilidad hacia atrás
468
+ - ✅ **Sin breaking changes**: Comportamiento automático y transparente
469
+ - ✅ **Opcional**: Si no hay concurrencia, funciona igual que antes
470
+ - ✅ **Configurable**: `maxRetries` puede ajustarse por usuario o config
471
+
472
+ ### Manejo de Edge Cases
473
+ - ✅ **Timeout de colas**: Las colas no deben quedarse esperando para siempre
474
+ - ✅ **Error en operación anterior**: Si la op anterior falló, la siguiente debe proceder
475
+ - ✅ **Limpieza de colas**: Las colas se limpian automáticamente cuando terminan
476
+
477
+ ## 🎯 Criterios de Éxito
478
+
479
+ ### Funcionales
480
+ - ✅ Los adjuntos se agregan exitosamente incluso con concurrencia
481
+ - ✅ No se observan más errores TF26071 en producción
482
+ - ✅ El sistema maneja automáticamente los reintentos
483
+ - ✅ Las operaciones a diferentes WIs se ejecutan en paralelo
484
+
485
+ ### No Funcionales
486
+ - ✅ Latencia aceptable (< 5s adicional con retry)
487
+ - ✅ Sin memory leaks en sistema de cola
488
+ - ✅ Logs claros y accionables
489
+ - ✅ Tests con > 80% de cobertura
490
+
491
+ ## 📞 Risk Assessment
492
+
493
+ ### Riesgos
494
+ | Riesgo | Probabilidad | Impacto | Mitigación |
495
+ |---------|-------------|-----------|-------------|
496
+ | Retry infinito con TF26071 persistente | Baja | Alta | Límite de 3 reintentos por defecto |
497
+ | Memory leak en colas no limpiadas | Baja | Media | Limpieza automática en `finally()` |
498
+ | Performance degradation con muchos retries | Media | Baja | Backoff exponencial y límite de reintentos |
499
+
500
+ ## 📊 Estimación de Esfuerzo
501
+
502
+ ### Complejidad Técnica: Media
503
+ - Implementación de sistema de cola: 2-3 días
504
+ - Lógica de retry con re-fetch: 1-2 días
505
+ - Integración y testing: 2-3 días
506
+ - Documentación: 1 día
507
+
508
+ **Total estimado**: 6-9 días de desarrollo
509
+
510
+ ---
511
+
512
+ *Este PRD establece la solución completa para el problema de concurrencia en `ado_add_attachment`, eliminando los errores TF26071 y proporcionando una experiencia de usuario robusta y transparente.*
@@ -0,0 +1,164 @@
1
+ # Bug Fixes v2.4.2 - v2.4.5
2
+
3
+ Serie de correcciones críticas que mejoran la estabilidad y funcionalidad del MCP Azure DevOps.
4
+
5
+ ## 🐛 Correcciones Incluidas
6
+
7
+ ### v2.4.2
8
+ - **Version bump**: Preparación para serie de correcciones
9
+ - **Cambios**: Actualización de versión en package.json
10
+
11
+ ### v2.4.3 (Commit 04e1f3a)
12
+ - **Fix(attachments)**: Clarify name parameter description in `ado_add_attachment`
13
+
14
+ #### 📝 Detalles
15
+ **Problema**: La descripción del parámetro `name` en la herramienta `ado_add_attachment` no era clara sobre su comportamiento.
16
+
17
+ **Solución**:
18
+ - Actualizar la descripción para aclarar que el parámetro `name` es opcional
19
+ - Documentar que por defecto usa el nombre del archivo original cuando no se especifica
20
+ - Mejorar la claridad de la documentación para los usuarios
21
+
22
+ **Código cambiado**:
23
+ ```typescript
24
+ // Antes: "Nombre del archivo (opcional)"
25
+ // Después: "Nombre del archivo (opcional, se usa el nombre del archivo si no se especifica)"
26
+ ```
27
+
28
+ ### v2.4.4 (Commit 82d31fa)
29
+ - **Fix(attachments)**: Preserve fileName parameter in attachment URL
30
+
31
+ #### 📝 Detalles
32
+ **Problema Crítico**: Los nombres de archivos adjuntos no aparecían correctamente en Azure DevOps boards y listas.
33
+
34
+ **Causa Raíz**:
35
+ - Cuando se subía un adjunto, Azure DevOps devolvía una URL con parámetro `?fileName`
36
+ - Formato: `https://dev.azure.com/{org}/{project}/_apis/wit/attachments/{id}?fileName={name}`
37
+ - El MCP estaba descartando este parámetro al reconstruir la URL para vincular
38
+
39
+ **Impacto**:
40
+ - Los adjuntos aparecían sin nombre en Azure DevOps boards
41
+ - La experiencia de usuario era confusa
42
+ - La funcionalidad principal de adjuntos estaba comprometida
43
+
44
+ **Solución**:
45
+ ```typescript
46
+ // uploadAttachmentRest: Agregar comentario clarificador
47
+ // La URL devuelta por Azure DevOps ya incluye el parámetro fileName
48
+ // formato: https://dev.azure.com/{org}/{project}/_apis/wit/attachments/{id}?fileName={name}
49
+
50
+ // ado_add_attachment: Usar URL completa devuelta por Azure DevOps
51
+ const attachment = await uploadAttachmentRest(filePath, fileName);
52
+ attachmentLinkUrl = attachment.url; // Usar la URL completa con ?fileName incluido
53
+ ```
54
+
55
+ **Cambios Implementados**:
56
+ 1. Modificar `uploadAttachmentRest` para agregar comentario sobre el formato de URL
57
+ 2. Modificar `ado_add_attachment` para usar la URL completa devuelta por Azure DevOps
58
+ 3. Declarar `attachmentLinkUrl` en el scope correcto para evitar errores de referencia
59
+ 4. Agregar cláusula `else` para lanzar error cuando no se proporciona ni `filePath` ni `attachmentUrl`
60
+
61
+ **Resultado**:
62
+ - ✅ Los nombres de archivos ahora aparecen correctamente en Azure DevOps
63
+ - ✅ La experiencia de usuario es más clara
64
+ - ✅ La funcionalidad principal de adjuntos trabaja como se espera
65
+
66
+ ### v2.4.5 (Commit 92b9db2)
67
+ - **Fix(env)**: Initialize currentProject with environment variable
68
+
69
+ #### 📝 Detalles
70
+ **Problema Crítico**: Race condition en llamadas REST cuando `currentProject` estaba vacío.
71
+
72
+ **Causa Raíz**:
73
+ ```typescript
74
+ // Línea 320 (antes):
75
+ let currentProject: string = ""; // ❌ Inicializado como string vacío
76
+
77
+ // En autoConfigureFromEnv():
78
+ currentProject = project || ""; // Solo se llena cuando se llama
79
+ ```
80
+
81
+ **Problema**:
82
+ - Las herramientas MCP podían ser llamadas antes de que `autoConfigureFromEnv()` completara
83
+ - Las llamadas REST en `uploadAttachmentRest()` fallaban cuando `currentProject` estaba vacío
84
+ - URL de construcción fallaba: `${baseUrl}//_apis/wit/attachments` (falta nombre del proyecto)
85
+
86
+ **Impacto**:
87
+ - Las herramientas MCP fallaban intermitentemente al inicio
88
+ - La experiencia de usuario era inconsistente
89
+ - Las operaciones con adjuntos fallaban con error de URL inválida
90
+
91
+ **Solución**:
92
+ ```typescript
93
+ // Línea 91 (después):
94
+ let currentProject: string = ENV_ADO_PROJECT || ""; // ✅ Inicializar con variable de entorno
95
+
96
+ // Asegura que el nombre del proyecto está disponible desde la inicialización del módulo
97
+ // Elimina la condición de carrera con autoConfigureFromEnv()
98
+ ```
99
+
100
+ **Cambios Implementados**:
101
+ 1. Modificar inicialización de `currentProject` para usar `ENV_ADO_PROJECT || ""`
102
+ 2. Asegurar disponibilidad del nombre del proyecto desde la carga del módulo
103
+ 3. Eliminar condición de carrera con `autoConfigureFromEnv()`
104
+
105
+ **Resultado**:
106
+ - ✅ Las llamadas REST API funcionan correctamente con variables de entorno
107
+ - ✅ No más condiciones de carrera al inicio
108
+ - ✅ La experiencia de usuario es consistente desde el primer uso
109
+
110
+ ## 📊 Impacto General
111
+
112
+ ### Estabilidad
113
+ - **3 correcciones críticas** que mejoran la confiabilidad del MCP
114
+ - **Eliminación de bugs** que afectaban la experiencia de usuario principal
115
+ - **Mejoras en manejo de errores** para mensajes más claros
116
+
117
+ ### Funcionalidad
118
+ - **Adjuntos**: Ahora funcionan correctamente con nombres apropiados
119
+ - **Configuración**: Eliminación de condiciones de carrera
120
+ - **Documentación**: Mejoras en claridad para usuarios
121
+
122
+ ### Compatibilidad
123
+ - **100% compatible** con versiones anteriores
124
+ - **Sin breaking changes**
125
+ - **Migración transparente**: Solo actualizar la versión
126
+
127
+ ## 🚀 Instalación
128
+
129
+ ```bash
130
+ npm install @slorenzot/mcp-azure@2.4.5
131
+ ```
132
+
133
+ ## 🔄 Migración desde v2.4.1
134
+
135
+ Esta versión es **100% compatible** con v2.4.1. Simplemente actualiza:
136
+
137
+ ```bash
138
+ npm update @slorenzot/mcp-azure
139
+ ```
140
+
141
+ ## ✅ Testing Realizado
142
+
143
+ - ✅ Adjuntos ahora aparecen con nombres correctos en Azure DevOps boards
144
+ - ✅ Eliminación de condiciones de carrera al inicio del servidor
145
+ - ✅ Manejo correcto de variables de entorno
146
+ - ✅ Compatibilidad verificada con configuraciones existentes
147
+ - ✅ Mensajes de error mejorados para usuarios
148
+
149
+ ## 🐛 Problemas Resueltos
150
+
151
+ - ❌ Adjuntos sin nombres en Azure DevOps boards (v2.4.4)
152
+ - ❌ Condiciones de carrera al inicio (v2.4.5)
153
+ - ❌ Descripción confusa de parámetro name (v2.4.3)
154
+
155
+ ## 🔮 Roadmap
156
+
157
+ Esta versión establece una base sólida para v2.6.0 que incluirá:
158
+ - Sistema de configuración jerárquico (.mcp.json → variables de entorno)
159
+ - Manejo de errores robusto con validaciones avanzadas
160
+ - Codificación URL inteligente para nombres de proyecto
161
+
162
+ ---
163
+
164
+ *Serie de correcciones críticas que transformaron la estabilidad y funcionalidad de adjuntos en el MCP Azure DevOps.*