Historial de cambios
GitHubCada cambio relevante, versión a versión. El mismo archivo se publica en el repositorio y está traducido a los 19 idiomas.
[1.254.0] — 2026-10-24
Una versión de endurecimiento desde la ronda de revisión con el modelo máximo: el snapshot del escáner nunca más pierde filas, once violaciones invariantes de la capa LLM están corregidas, veintitrés must-fix del SPA aterrizan, y el ATS Paylocity se une al escáner — el registro ahora entrega 121 fuentes (116 EN + 5 RU) con 116 adaptadores.
Añadido
- Paylocity ATS (padre #5687): tableros por tenant
recruiting.paylocity.com, tablero muerto 302 → JobNotFound mapeado a 404 para que la cuarentena retire la entrada, topemax_jobscon bandera de truncamiento. Registro: 121 fuentes = 116 EN + 5 RU · 116 adaptadores; los gates de recuento regenerados.
Corregido
- Seguridad del snapshot del escáner (R-12): un escaneo que no intenta nada ahora CONSERVA el snapshot (
nothing scanned); un reemplazo completo arrastra las filas antiguas de las fuentes fallidas — selladas por entrada — re-pasadas por los filtros ACTUALES, aterrizando solo enfiltered. - Once invariantes de la capa LLM: directiva de idioma que abre Y cierra los prompts deep/mode; subprocess Gemini con
--lang, 502 en salida no cero, stderr sin filtrar al cliente, 300 s y usage registrado; manual fallback con URL; truncamiento visible en/api/mode; el spend tier ya no lo cambia el cliente; la desconexión del cliente aborta la llamada al proveedor; el cortador de encabezados repetidos ya no destruye reportes válidos; extractores score/legitimacy alineados; falsos-aceptes uk y árabe tensionados. - Una ronda de revisión Opus 5-5 SOBRE las correcciones (8 imperativos): registro multi-letra del cortador, terminadores más amplios, abort del subprocess Gemini, atribución sellada del arrastre, re-filtrado de filas arrastradas, nivel de referencia por letras distintas.
- Veintitrés must-fix del SPA en las vistas de sourcing y de asistencia (pager de una página, teclado de pipeline, restauración de filtros, resets de select, fugas de listeners, cierre SSE, teardown del runner, fetch pelado, cargas fallidas que parecían vacías, colisión global, preservación de foco; deep-link de help muerto, fugas de help, re-armado de blank-overwrite, single-flight de ejecuciones pagas, fines silenciosos de stream, spinners eternos, filas fantasma, duplicados de docs-fab + idioma).
Notas
- Toda la ronda fue revisada por Opus 5-5 con máxima exigencia — los puertos pre-merge (21 hallazgos, 13 corregidos) y los propios fixes post-hoc (8 imperativos, todos aquí con tests rojo→verde).
- Piso de tests: 5262 → 5634 (372 specs nuevas en fuentes, escáneres, capa LLM y SPA).
[1.253.1] — 2026-10-24
Dos hallazgos HIGH de seguridad de la revisión con el modelo máximo están corregidos: las URLs literales IPv6 ya no sortean el guardia SSRF del escáner, y el regex de escape del markdown del CV ya no retrocede cuadráticamente.
Corregido
- Los literales IPv6 burlaban el guardia SSRF (
http-json.mjs): un literal IPv6 entre corchetes ([::1],[::ffff:169.254.169.254],[fd00:ec2::254]) hacía quedns.lookuplanzara y el guardia fallara abierto. Ahora se quitan los corchetes y el literal se clasifica víaisIP— sin DNS, sin fail-open. - ReDoS en el escape del markdown CV (
security.mjs): el patrón retrocedía cuadráticamente —<+ ~100k espacios costaban ~16 s; ahora ~1 ms con una clase plana acotada. Todo lo escapado antes sigue escapándose.
Notas
- Ambos fijos llevan tests rojo→verde (3 casos IPv6 nuevos + ReDoS con cota temporal; 5511 tests, +7).
- Los archivos fuente del escáner no están afectados — el guardia vive en el transporte compartido.
[1.253.0] — 2026-10-24
Diez nuevas fuentes de empleo se unen al escáner — Atlassian, BWI, Computrabajo, Dover, Google, Kalibrr, Magneto365, Platsbanken, Tech in Asia y Zoho Recruit — el registro ahora entrega 120 fuentes (115 EN + 5 RU) con 115 adaptadores, y el role matcher mantiene separados los títulos que difieren solo por un código corto en mayúsculas.
Añadido
- 10 fuentes portadas del padre 1.38.0, cada una con su adaptador de fetch-walk y una suite aislada de CI (fetchImpl falso, cero red): Atlassian (feed JSON único sin auth, URLs iCIMS, allowlist https+icims.com), BWI (ItemList JSON-LD, JSON.parse-first), Computrabajo (LATAM, decodificación de entidades, «remoto» lee remote), Dover (paginación DRF por tenant, render
Remote (International), tableros validados por UUID), Google Careers (primeras páginas por frase, el empleador de la tarjeta gana, dedup entre consultas), Kalibrr (agregador SEA, id del feed como id de fila), Magneto365 (LATAM, ritmo inter-página fijo), Platsbanken (API JobTech de Suecia, ventanas por palabra clave), Tech in Asia (claves Algolia descubiertas del build del sitio), Zoho Recruit (board de hidden-input, enriquecimiento de detalle acotado, moneda solo ISO-4217). - Recuento del registro: 120 fuentes = 115 EN + 5 RU · 115 adaptadores; los cinco gates de recuento regenerados del registro vivo; el fallback del dropdown
#/scan, los enlaces de cvstart.org y help §17 ×18 siguen.
Corregido
- role-matcher (puerto #5688): los títulos que difieren solo por un código corto en mayúsculas («HR BP» vs «HR GP», «AWS» vs «GCP», incluso en escrituras mixtas) ya no se emparejan por fuzz como la misma vacante; los códigos se leen del título crudo con exclusiones de palabras-ruido y el guard va justo antes del ratio de Jaccard.
- Los puertos sobrevivieron a una revisión Opus 5-5 de máxima exigencia (21 hallazgos): throws en tableros silenciosamente vacíos por deriva de marcado (google/bwi), un escaneo pre-abortido lanza en vez de devolver
[], orden decode-then-strip en los scrapers LATAM (la forma XSS strip-then-decode), https+allowlist en las URL de fila de atlassian, asserts de host que rechazan puertos, parse de páginas tardías dentro del try (los parciales sobreviven) y honestidad de tests (presupuestos contados exactamente, contratos de coerción fijados con verdad). 13 hallazgos corregidos; la cola [spec] se rastrea aparte.
Notas
- Los cinco RU siguen siendo web-ui-only (sin adaptadores upstream — a propósito); cada fuente EN tiene su adaptador.
- Ratchet de cobertura: los seis puertos mayores subidos a ≥95 % branch (27 casos nuevos); el código muerto defensivo restante queda documentado por archivo.
- Las fuentes nuevas se seleccionan con
provider: <slug>en portals.yml — nada cambia para configuraciones existentes.
[1.252.1] — 2026-10-17
El banner de versión de la wiki ahora lo verifica un script en lugar de un barrido manual — la regresión v1.252.0 solo lo detectó a mano — y la directiva de locale anula explícitamente el language.output del perfil y el idioma de la vacante.
Añadido
scripts/wiki-banner-check.mjs— el gate que pedía la regresión: lee cadaHome*.mden un clon de la wiki y compara el banner conpackage.jsony con los contadores VIVOS del registro (scanner adapters del registro de portales — 105, no los 109 caducos del banner; route modules deserver/lib/routes/; UI locales del conjunto CHANGELOG). El regex del banner es agnóstico de idioma por FORMA: las páginas Home localizadas traducen la etiqueta y los contadores («Текущий релиз», «Aktuel udgivelse», «адаптеров сканера», «enhedstests») y sus cifras habían derivado más (5177) que la página EN (5245).--tests Ntoma el número que eltest:cirecién impreso;--check-onlyprohíbe el clon implícito para uso offline. Test de fixture offline entests/wiki-banner-check.test.mjs.
Corregido
- El banner de la wiki Home ×18 vuelve a estar sincronizado (se envía con esta versión): el barrido de v1.252.0 cambiaba badges y enlaces pero nunca la línea del banner, y nada mecánico lo señalaba. El gate de arriba lo convierte en comprobación bloqueante de release.
- La directiva de locale fija la prioridad del idioma (
buildLocaleDirective): «This UI locale overrides the profile’slanguage.outputsetting and the job posting’s own language». La propia regla 6 del padre manda escribir enlanguage.output— sin la frase de prioridad el modelo resolvía las dos instrucciones contradictorias de forma inconsistente según el locale, que era la dispersión de la columna de idioma en v1.252.0. Fijado portests/locale-directive-language-output.test.mjssobre el texto de la directiva (17 locales no-EN + EN vacío).
Notas
- Observación de FORMA en ta evaluate (§3, vigilar): la primera ejecución en vivo de v1.252.0 produjo un evaluate de 48 336 caracteres sin Block G ni SCORE_SUMMARY — con forma de bucle, no de idioma (CAR-70 es de idioma). Si se repite en el próximo release, tendrá su propia tarea en Linear, separada de CAR-70.
- Según el FIX-PROMPT: el padre queda en solo-lectura; la anulación vive enteramente en la directiva de la UI.
[1.252.0] — 2026-10-17
El fork del padre está sincronizado con career-ops-hq/career-ops — 264 commits de upstream, padre 1.36.0 → 1.37.0 — con ambas divergencias del fork defendidas, y el despliegue vuelve a fijar el padre en ejecución a esa base.
Añadido
- Modo de mercado vietnamita (vi) desde upstream (
modes/vi/,tuyen-dung/ung-tuyen): BHXH/BHYT/BHTN, thử việc, lương tháng 13, trợ cấp thôi việc — el mercado nº 21 en la tabla de modos del padre. - Proveedor del tablero Tech in Asia (upstream): lee el índice Algolia público
job_postingsque la propia UI de empleo de techinasia.com consulta — un tablero SEA de startups, ~300 vacantes vivas en el muestreo, 273 indonesias. Las claves se descubren en cada ejecución desde el build del sitio, el enlace propio del empleador gana a la página del tablero y el salario se queda sin adjuntar (los campos de salario del índice son banderas, no dinero). - Ocho nuevos documentos de señales de legitimidad Block G (
modes/oferta-signals/08–17): país en terminología de beneficios, etiqueta de ubicación de plataforma de terceros, licencias de agencias, exceso de requisitos migratorios, contenido prohibido por jurisdicción, amplitud del rango pay-transparency, pregunta al abogado sobre salario mínimo, divulgación de AI-screening, divulgación de contrato fijo, poder adquisitivo en reubicación.
Cambiado
- Endurecimientos del padre que viajan con la base: la comprobación story-provenance funciona por unidades y frases y mantiene las cifras
user-cannot-confirmfuera de negociación, ATS y salida de entrevista; el fact-gate del CV se ejecuta en cada renderizador y lee afirmaciones reformuladas y estructuradas; LaTeX rechaza slots extraídos caducados antes de escribir; el barrido launchd de follow-ups se omite cuando los follow-ups están apagados; update-system mantiene su escaneo de ediciones locales dentro del límite de argv. - Las divergencias del fork sobrevivieron al merge:
providers/telegram.mjs(parser exclusivo del fork, ADR-0001) y elLOCATIONISH_REUnicode lookaround entelegram-channel.mjs(ADR-0002). El documento de tableros ahora enumera la filatelegram-channelmejorada de upstream, la filatelegramexclusiva del fork, Tech in Asia y elmax_pagespor defecto de Tencent subido 20 → 100.
Notas
- Sin cambios de comportamiento del SPA en esta versión: es el tren de documentación más la re-fijación del padre. El despliegue ahora corre el padre 1.37.0; la tabla de estados canónicos no es tocada por el delta del padre — los estados del tracker se renderizan igual que antes.
- En el merge de upstream se resolvieron dos conflictos:
docs/SUPPORTED_JOB_BOARDS.md(ambas filas telegram conservadas) ytest-all.mjs(elcopyDirSyncbasado en walkTree de upstream subsume el guard manual de checkouts anidados por.gitdel fork; la lista de import es la unión).
[1.251.1] — 2026-10-12
El filtro de especialización ahora coincide solo con palabras DISTINTIVAS del rol — la antigüedad y los sustantivos genéricos de trabajo ya no coinciden una vacante por sí mismos — y remote-qa separa forma de idioma para que un deriva nunca marque en rojo A–G.
Corregido
- El filtro de especialización elimina la antigüedad y los sustantivos genéricos de su comparador: «Senior PHP Developer» coincidía con «Senior Java Developer» por el Senior/Developer compartido. El comparador conserva solo los tokens distintivos (PHP, Go, Platform, SRE, Backend…); un rol sin tokens distintivos («Software Engineer») retrocede a la coincidencia de frase completa. Los límites de palabra se mantienen: «Go» nunca coincide con «Golang» ni «Google».
- remote-qa separa forma de idioma: el indicador A–G se juzga solo por códigos de bloques/SCORE_SUMMARY/looping; un deriva
report not in <locale>pasa a su propio indicador y columna de warnings (la tabla gana la 4ª columna que faltaba; los códigos se guardan por fila). - La barra del mapa es un verdadero 50/50 a 390 px (la división 58/42 dejaba el buscador en 124 px y truncaba su marcador), y la regla CSS muerta
.job-map-job-selectse ha eliminado. - CodeQL 552 — la variable sin uso
OVERLAYSen el test design-qa eliminada.
Notas
- Las millas siguen aplazadas por el propietario. El deriva de idioma ta queda como limitación DOCUMENTADA del backend hermes (2–26 % de prosa entre corridas; A–G se sostiene): remote-qa la reporta como hallazgo esperado. La lista de tokens genéricos es mínima a propósito — ampliarla es una línea por idioma.
[1.251.0] — 2026-10-12
El select del mapa ahora filtra por ESPECIALIDAD de los ajustes (config/profile.yml → target_roles.primary): elige «Product Manager» o «Backend Engineer (Go / PHP)» y el mapa conserva solo las vacantes cuyo título lleva las palabras de ese rol. El localizador de lista de vacancias de v1.250.0 se elimina — era la herramienta equivocada (corrección del propietario).
Añadido
- Filtro de especialización: el select lista las especializaciones de los ajustes (target_roles.primary); elegir una conserva solo las vacantes cuyo título lleva uno de los tokens distintivos del rol (por límites de palabra, sin mayúsculas — «Go» nunca coincide con «Google»); Reset devuelve todas.
- El localizador de lista de v1.250.0 se elimina — el sustituye: filtro por especialización, no lista de vacantes (corrección del propietario).
Cambiado
- Two mobile-map height specs: the floor moves 600 -> 540 px (the toolbar takes ~90 px of the viewport).
Notas
- Las millas siguen aplazadas por el propietario. El matching es deliberadamente SOLO por título con las palabras de los ajustes: empresas o descripciones meterían vacantes ajenas en la especialización. Una vacante fuera de todos los roles configurados sigue visible en «Todas».
[1.250.0] — 2026-10-12
La página del mapa se vuelve operativa: un localizador de vacancias sobre el mapa vuela hacia cualquier vacante visible y abre su tarjeta emergente (también en escritorio), y una búsqueda de texto acota el mapa por empresa o puesto — ambas sobreviven a la recarga.
Añadido
- Localizador de vacancias: un select sobre el mapa lista las vacantes visibles (post-filtro) ordenadas por empresa+título; elegir una vuela al círculo y abre su tarjeta — también en escritorio.
- Búsqueda de texto: reduce el mapa por empresa+puesto (sin mayúsculas en la comparación; el campo guarda el caso escrito), persiste en map-prefs y Reset lo limpia.
- El FAB ya no cubre la atribución en
#/mapa todos los anchos.
Corregido
- Dos especificaciones de altura móvil: el suelo pasa a 540 px (la barra toma ~90 px del viewport).
Notas
- Las millas quedan aplazadas por el propietario al siguiente release. Las 4 claves i18n nuevas entran en los 18 diccionarios y el snapshot ensamblado se regenera. El localizador siempre abre la pre vista (navegar lo derrotaría); el círculo se encuentra por la referencia inversa __job — no _point, que L.Circle ya posee.
[1.249.0] — 2026-10-11
El mapa de empleos ahora prioriza la relevancia: una escala tranquila de turquesa a gris destaca las mejores ofertas, los filtros de puntuación y trayecto se conservan, el móvil usa un mapa a pantalla completa con vista previa al tocar y el tracker comparte filtro y orden de trayecto.
[1.248.8] — 2026-10-11
Paridad con el padre: el fork y web-ui se sincronizan con el delta de 115 commits del upstream — una nueva fuente PageUp, las celdas URL del rastreador se analizan como enlaces markdown, el estado Assessment — más el trabajo de idioma ta/ja: los informes en bucle se cortan y se nombran, el esqueleto de encabezados mantiene A–G estable, y la cuota de escritura se lee solo en prosa.
Añadido
Corregido
- Nueva fuente PageUp (105 adaptadores EN / 110 fuentes); las celdas URL del rastreador se analizan como enlaces markdown (el href es la clave de deduplicación); el estado Assessment llega vía states.yml.
- Informe en bucle cortado y nombrado (
report looping (block B repeated)), esqueleto de encabezados## A) … ## G)para no-EN. - La cuota de escritura lee solo prosa (tablas, SCORE_SUMMARY, código y URLs fuera) — un informe japonés con tablas latinas ya no flaquea.
- /api/evaluate avisa
report not in <locale>y remote-qa imprime los códigos en una columna.
Notas
- No portado (documentado): la infraestructura scan-ats-full (fetchListing/listingIdentity/_connection-scope — web-ui no tiene barrido reverse-ATS), el grabador de historial de vencidos en liveness (web-ui no escribe scan-history; el planificador puro se porta cuando aparezca un escritor), coverage-check/discover/story-bank/pdf –ats/text mode/domain_filter (solo CLI), el fix de req-id en dedup-tracker (web-ui no tiene dedup-tracker), README.vi.md (el conjunto de locales de web-ui es propio). El fork del padre defiende ADR-0001/0002: telegram.mjs sobrevive, LOCATIONISH_RE Unicode se mantiene (el upstream lo revirtió a \b ASCII).
[1.248.7] — 2026-10-11
La función de la cabecera del informe ahora lee la convención real del padre — # Evaluation: {Company} — {Role} — en lugar de asumir # <Función> — <Empresa>, así que el rastreador ya no lleva «Evaluation:
Corregido
- La etiqueta localizada se quita primero (
Evaluation:,Оценка вакансии:y 16 variantes más); tras la etiquetaCompany — Role→ la función es el lado derecho; sin etiquetaRole — Company→ la izquierda; la parte que nombra a la empresa decide; la función nunca es la empresa ni una palabra de etiqueta — el respaldo es la línea de la publicación limpia. - cleanup-plan suma los N de las líneas (
— N dirty-URL line(s)), no cuenta archivos — un archivo con 5 URLs sucias daba 1, no 5. - Un informe en el idioma equivocado avisa: el prompt abre Y cierra con la instrucción obligatoria de idioma (todo el informe, tablas incluidas), y el pipeline valida el idioma del informe guardado — el deriva aparece como aviso
report not in <locale>. - Sin charla del modelo antes del H1 — el informe empieza en su línea de encabezado.
Notas
- No portado y sin tocar (verificado en el regression v1.248.6): el fetch embed, la estructura de líneas del post («Компания : X»), el dedup «## Rejected», el rechazo de la raíz de dominio antes del fetch, la honradez de recon/link-check y los modos cleanup. La validación de idioma es un aviso — el informe (ya validado en contenido A–G + puntuación) se guarda; el operador ve la deriva y puede repetir la evaluación.
[1.248.6] — 2026-10-10
Pulido posregresión de la cadena de Telegram: una publicación real conserva su estructura de líneas (la etiqueta «Компания : X» y la línea del rol se encuentran donde realmente están), el rol del rastreador proviene del encabezado del informe en lugar de una línea cruda de la publicación, y las superficies operativas (recon, cleanup-plan, link-check) informan lo que dicen que informan.
Corregido
- Una publicación de t.me ya no se pega en una sola línea:
<br>(y</blockquote>) ahora se convierten en salto de línea antes de quitar las etiquetas, y las entidades HTML numéricas se decodifican; el patrón de la etiqueta de empresa acepta «Empresa : X» con espacio antes de los dos puntos. - El rol del rastreador viene del encabezado del informe: el H1 sigue la convención
# <Rol> — <Empresa>(se toma el lado del rol); la línea de respaldo se limpia — sin hashtags, sin emojis, ≤80 caracteres. ## Rejectedya no se duplica: la sección se crea una vez; cada rechazo añade su línea dentro de la sección existente.- La raíz de dominio se rechaza antes de la solicitud: la comprobación de
pathnamese ejecuta antes del fetch — una URL basura no cuesta nada. - Recon calcula la señal del contrato en lugar de afirmarla:
honours rejected:esyessolo si elauto-pipeline.mjsdesplegado contienemarkPipelineRejected; el contador de marcas ya no imprime0dos veces. - cleanup-plan informa números por categoría: informes t-role a mover, líneas dirty-URL en pipeline, filas test-row en el rastreador — en lugar de un único
grep -cque contaba su propia línea de éxito. - link-check trata los 5xx de github.com como no verificados: tres ejecuciones seguidas dieron 503 en enlaces blob desde los runners de Actions mientras otras redes los veían 200 — detrás del muro de bots, no como enlaces rotos.
Notas
- No portado y sin tocar (verificado en el regression de v1.248.5): el propio embed-fetch, el contrato de salto en pipeline, los tests offline _setLookup, la aceptación de ordinales uk, los límites de palabra en el filtro de rol y el modo cleanup-apply. El conteo por categorías de cleanup-plan lee las etiquetas de salida del propio script — renombrarlas rompería los contadores en silencio.
[1.248.5] — 2026-10-10
Reforzamiento posregresión: las entradas individuales de t.me ahora obtienen el propio texto del post (forma embed), una entrada rechazada sale del pool del temporizador de evaluación del lado del servidor, y la numeración nativa de bloques en un informe ucraniano es aceptada — además, un test que usaba la red en secreto queda offline para siempre.
Corregido
- Las entradas de t.me por fin se evalúan: la página de un solo post solo sirve el encabezado del canal en HTML plano (~0,2 KB — «View in Telegram» y nada más), el modelo recibía una descripción vacía y cada entrada de Telegram moría en la evaluación. La obtención ahora reescribe
t.me/<canal>/<id>(y la forma de feedt.me/s/…) a la forma embed (?embed=1&mode=tme) — mismo host, validación SSRF sin cambios — y lee el post de.tgme_widget_message_text(respaldo:og:description). Sin texto del widget → rechazo en el paso fetch (telegram post has no text,rejected: true) antes de gastar tokens del LLM. - El temporizador de evaluación deja de pagar de nuevo por las entradas rechazadas (contrato del lado del servidor): una entrada rechazada se marca ahora en
data/pipeline.md— su línea sale de la valla de URLs y aterriza en una sección## Rejectedcon un motivo corto (idempotente, nunca duplicado). El temporizador toma URLs desnudas de la valla, así que una entrada marcada se salta de forma natural — sin depender de las interioridades del script del temporizador. Recon imprimepipeline rejected marks: N+honours rejected: yes (server-side)en lugar de buscar en el script. - Los informes ucranianos vuelven a pasar la validación A–G: un modelo uk numera los bloques según el orden de su propio alfabeto (А Б В Г Д Є Ж), y el paso de lookalike solo contaba А/В/Е — exactamente los bloques C, D, F, G faltaban en producción (2/2 reproducciones). El validador ahora acepta la secuencia ordinal ucraniana cuando los encabezados aparecen en orden (la misma disciplina que abjad para ar y devanagari para hi), y el prompt de evaluación para cada locale exige explícitamente
A–Glatinas. - La puerta de palabras clave de rol se activa por palabras, no por subcadenas: las raíces
linkedin.com/yx.com/pasaban la puerta porquelead/itvivían dentro del texto de interfaz de la página. Las palabras clave ahora coinciden en límites de palabras Unicode, y una raíz de dominio (URL sin ruta) se rechaza antes de la llamada al LLM. - Un test unitario ya no necesita la red en vivo: el test no-hints (
tests/auto-pipeline-jd-gate.test.mjs) sustituía el transporte HTTP pero seguía haciendo una consulta DNS real — verde en CI solo porque el runner tenía conectividad. safe-fetch ganó un hook_setLookup()(el propio resolvedor es reemplazable en tests; el código de producción nunca lo sobreescribe), y el test resuelve offline.
Añadido
- Modos de workflow
cleanup-plan/cleanup-applyen deploy.yml: la limpieza de basura QA en producción (post-qa-cleanup) se ejecuta del lado del servidor sin SSH desde un portátil. El registro lleva solo números — sin nombres de archivo ni vistas previas de líneas, porque el registro es público y los nombres de archivo contienen nombres de empresas. El paso Verify se omite para los modos de limpieza.
Notas
- No portado: el compilador de ubicaciones por límites de palabras y la tabla de estados USPS del padre (documentado desde v1.248.1). El contrato de salto de entradas rechazadas vive en el servidor (marcado en data/pipeline.md), no en el script del temporizador — está fuera del repositorio y no se modifica a propósito.
[1.248.4] — 2026-10-10
La cadena de resolución de rol para entradas de Telegram: una publicación cirílica con una vacante real ahora produce un informe con nombre en vez de un archivo unknown-role rechazado — y la llamada al LLM solo se dispara para entradas que vale la pena evaluar.
Corregido
- La cadena de rol funciona para publicaciones cirílicas: la lista de palabras clave suma desarrollador/programmista/ingeniero/analista/manager/руководитель/timlid/lid/diseñador/tester/QA/arquitecto/especialista/director/DevOps/SRE (la antigua era solo EN: 0 de 40 publicaciones reales obtenían un rol), se consulta la cabecera del informe (
parseReportHeader— el modelo escribe el rol en el H1) y la primera línea con sentido del post es el último recurso. - La puerta de identidad se movió ANTES de la llamada al LLM: una entrada sin empresa ni rastro de rol falla en el paso fetch (
rejected: true) — no se gastan tokens en una evaluación que se iba a rechazar de todos modos. t.me/s/<canal>/<id>ya no nombra la empresa «S» — se salta el segmento de vista previa; ambas formas de enlace resuelven al canal.- Las autoetiquetas «Компания: X» en publicaciones de Telegram se respetan (y «…в компании X» a mitad de frase para hosts de mensajería).
Añadido
- Los fallos del auto-pipeline llevan
rejected: trueen el error SSE — el temporizador de evaluación del servidor usa la marca para saltarse la entrada del pipeline en vez de reintentarla cada 2 horas.
Notas
- No portado: el compilador de localización con límites de palabra del padre y su tabla USPS (documentado desde v1.248.1).
- El bloque «Translations» del README se genera desde una lista canónica, con un test de paridad en CI (17 enlaces por archivo, el propio nunca enlazado) — ar/pl/uk ganaron el bloque; se eliminó la deriva de separadores
|/·.
[1.248.3] — 2026-10-10
Continuación de la ronda de regresión: el temporizador de evaluación ya no puede archivar bajo ningún concepto basura de Telegram sin nombre, la limpieza QA ya no borra ofertas reales de Telegram, la atribución del mapa vuelve a verse y los chips del panel leen ≥ 4.5:1.
Corregido
- El auto-pipeline valida ANTES de guardar: un informe sin puntuación 0–5 o sin bloques A–G termina la ejecución en el paso evaluate (
rejected: trueen la carga SSE) — sin archivo de informe ni fila del tracker. Las entradas de t.me que superaban la puerta de longitud de v1.248.2 produjeron dos basuras más tras esa versión; ahora las entradas sin nombre se rechazan concompany/role not identifiable. guessCompanyRoleya no inventa una empresa desde hosts de mensajería:t.me,telegram.me,vk.com,linkedin.comy amigos están en EMPTY_DOMAINS; una publicación de Telegram retrocede al nombre del canal, y una entrada sin nombre se rechaza en vez de archivarse comounknown-role.post-qa-cleanuplee primero la puntuación de la cabecera: auto-pipeline elimina el bloque SCORE_SUMMARY antes de guardar, así que la vieja comprobación solo-summary marcaba cada oferta de Telegram guardada como basura — en producción 8 de 41 llevan puntuación en la cabecera y--applylas habría borrado. La puntuación de cabecera (víaparseReportHeader) es ahora la señal primaria; el bloque queda como respaldo. El dry-run imprime el motivo por archivo.- La atribución de Leaflet se ve en #/map — el FAB de docs la cubría (violación de la licencia OSM); la esquina se eleva 96px.
- Los chips del panel superan 4.5:1 — las etiquetas
dash-chipy la variante--manualpasan a--foggy-strong(antes 4.31:1).
Añadido
- Los fallos del auto-pipeline llevan
rejected: trueen el error SSE — el temporizador de evaluación del servidor usa el indicador para saltarse la entrada del pipeline en vez de reintentarla cada 2 horas.
Notas
- El desvío ar/ja no se repitió en la re-ejecución (ver v1.248.2).
- La limpieza de producción sigue esperando un sí explícito del usuario (CAR-61c); con esta corrección la limpieza ya no puede borrar las ofertas reales de Telegram.
[1.248.2] — 2026-10-10
Ronda de regresión: el temporizador de evaluación ya no convierte basura del pipeline en «Last evaluation», el análisis de SCORE sobrevive a formatos reales y el tracker por fin habla tu idioma.
Corregido
- Los informes con JD vacío ya no se escriben. La puerta de JD del auto-pipeline sube de 50 a 200 caracteres antes de la llamada al LLM: las páginas-placeholder (
example.com/qa-v167-…) terminan en un error SSE + aviso en consola en vez de un informe…-t-role-<ts>.mdque el panel mostraba como «Last evaluation» (42 archivos en producción). - El análisis de SCORE_SUMMARY sobrevive a formatos reales: «4,2/5», «4.2 / 5», «4.2», «SCORE :» validan; una coma con 1–2 dígitos es decimal, con 3 es millares (el código antiguo leía «4,200» como 4.2 y pasaba la puerta 0–5). Un fallo registra solo la línea
SCORE:— nunca el contenido del informe (los logs de CI son públicos). - Los estados del tracker se traducen: pestañas y badges muestran etiquetas localizadas en las 18 locales; el valor canónico en datos, filtros y URL no se toca.
- ETA honesta: «⏱ ~30s» pasó a «~2–4 min» en evaluate/advisor/informe de mercado (el rango real es 86–285 s), y el subtítulo de evaluate lee A–G.
- Accesibilidad: el contador de API-keys de config y el del pipeline superan 4.5:1 (antes 4.31:1); un chip de faceta con valor vacío ya no se renderiza sin nombre accesible.
- Pulido de UI: el botón Delete de búsquedas guardadas se ve destructivo solo con una búsqueda seleccionada (si no, deshabilitado); la tarjeta EMAIL del perfil no se recorta a 1440 px; la píldora live-evals ganó espaciado icono/texto incluido RTL; el FAB de docs no cubre la atribución de Leaflet; el «?» de help ya no salta bajo las pestañas de stats; usage ganó aire entre subtítulo y pestañas; health ahora dice career-ops-ui.
Añadido
scripts/post-qa-cleanup.mjs: dry-run por defecto,--applycon copias automáticas (*.bak-<ts>, informes movidos aqa/cleanup-backup-<ts>/): filas ZZ-QA-TEST del tracker, informes*-t-role-*sin SCORE válido y entradasexample.comdel pipeline.
Notas
- El desvío ar/ja de la ronda de regresión no se repitió en la re-ejecución (ambos: lang ✓ + A–G ✓) — la entrada ta del language-check y el análisis de SCORE se envían como defensa en profundidad.
- La limpieza de producción espera un sí explícito del usuario: primero el plan en dry-run, luego
--apply.
[1.248.1] — 2026-10-09
location_filter gana los niveles always_allow y block_hard del padre (CAR-60): una cadena de ubicación multipaís sobrevive a una lista de bloqueo de hermanos, y un bloqueo explícito duro ya no puede rescatarse.
Corregido
always_allowvence ablock: conblock: [romania, poland, croatia]yalways_allow: [united kingdom], la celda combinada de workable «London, United Kingdom · Romania · Poland · Croatia» pasa: la región propia es una opción (el caso consumidor de workable #4806, ahora en forma paterna).block_hardvence aalways_allow: términos de nivel país que nunca deben rescatarse por un acierto de always_allow en otra parte de la cadena.strict: trueya cuentablock_hardcomo nivel restrictivo; la secciónlocation_filterde docs/help documenta ambos niveles ×18.
Notas
- No portado esta vez a propósito: el compilador de palabras clave de ubicación con límites de palabra del padre y su tabla de estados USPS (web-ui mantiene coincidencia de subcadena; la tabla sirve a configuraciones centradas en EE. UU. — archívalo bajo CAR-60 si hace falta). La forma de lista de permisos del test consumidor de workable queda como segundo veredicto junto al nuevo de forma paterna.
[1.248.0] — 2026-10-09
Paridad con el padre: cinco correcciones de proveedores/liveness portadas — los tableros que movieron su API siguen funcionando y un banner de cierre en su propia línea ya no se pasa por alto.
Corregido
- Gupy sigue a la API hasta
portal.gupy.ioy envía los nombres de filtro que la API lee de verdad (workplaceType/type): los nombres plurales antiguos se ignoraban en silencio;countryya no se envía (un país configurado genera un aviso con el nombre en vez de enviarse). Los empleadores confidenciales caen por coincidencia exacta de etiqueta («Confidencial Seguros» sobrevive) ymax_pagesse limita a 100: la API responde 400 pasado el offset 10 000, y el aviso de truncado ahora sugiere acotar palabras clave. - Workable conserva todos los países de una oferta multicountry: los hermanos bajo la misma URL de empleo se pliegan en una fila («London, United Kingdom · Romania · Poland · Croatia») en lugar de ganar el primer país.
- Liveness: un banner en su propia línea no es parte de la línea superior: «Sign in if you want to apply» / «Get notified when this position is filled» bajo una oferta se lee como la cláusula de cierre que es, mientras un genuino «until filled» a través de líneas sigue leyéndose activo.
- Radancy lee un elemento hermano
job-locationen el marcado antiguo de tarjetas (los layout de la familia Barclays ponen la localización tras</a>): las filas sin localización propia quedan vacías en vez de tomar la del vecino. - Los enlaces de respaldo de SmartRecruiters conservan el slug configurado: una oferta sin
reffiable sintetiza su URL pública desde el slug del tenant, nunca desde el nombre visible.
Añadido
- No portados, documentado: el arreglo
_httpde proxyfetchImpldel padre (web-ui no tiene rama de proxy: portarlo anularía la comprobación de identidad del guardia DNS-rebinding y añadiría una dependencia undici que el repo evita a propósito);verify-ats(+226 líneas) ycv-factsdel padre (web-ui implementa su propio scoring ATS y parseo de CV); el dashboard Go TUI y scripts CLI del padre; un reorden de imports en Teamtailor (cosmético).
Notas
- Brecha conocida como seguimiento: el tier
always_allowdel filtro de ubicación del padre (el test consumidor de workable sobrevive a un bloque UK gracias a él) no existe en web-ui; el puerto expresa el mismo veredicto vía un test de allow-list. Archivado para un cambio aparte.
[1.247.0] — 2026-10-09
La cola del barrido de diseño CAR-49…58 está completa: todos los menores y pulidos restantes, más el mapa oscuro que la versión anterior aplazó a propósito.
Corregido
- El mapa renderiza un tema oscuro real. Zoom/capas/atribución se oscurecieron en v1.246.0; ahora los mosaicos también siguen: el modo oscuro dibuja la capa OSM con una inversión CSS (sin claves de proveedor) y hay un preajuste Carto-dark con clave vía
MAP_TILE_DARK_URL(su host está permitido en el CSPimg-src). La capa se remonta en vivo al cambiar de tema. - Los callouts amarillos tienen variante oscura (
config,batch, aviso del hero) con la mecánica de tokens de v1.246.0 — y se cazó una regresión:.callout apisaba a.btn-primary; ahora con ámbito:not(.btn). - La URL de la tarjeta de información de Apply ya no se corta a mitad de palabra.
- Los botones de cabecera del CV usan un solo sistema de iconos (texto; fuera el mezclillo 📁/📄/💾).
- Las pestañas de estado del tracker tienen un solo estilo de caja — frase normal, con matices por locale (CJK/ar/hi/ta sin formas de género).
- Los iconos de la barra superior son un único set de SVG de trazo — fuera 🔔/🌙/🩺; el botón de tema dibuja sol/luna con CSS.
- La tarjeta Pipeline del panel pierde su resaltado rosa (anillo solo en hover/focus).
- Borrar búsquedas guardadas se ve destructivo (
.btn-danger, no un fantasma neutro). - La vista previa de pipeline tiene estado vacío real (título + pista + CTA, patrón tracker).
- El metadato de tiempo/costo es un patrón único:
UI.pageMetapinta ⏱ + costo en una línea bajo el título (evaluate/deep/auto/mode-page/orientation). - Auto-pipeline pierde su ✨ — el único h1 decorado vuelve al borde izquierdo común.
- La URL de docs de batch es un enlace en línea.
- interview-digest / orientation tienen estados vacíos diseñados (título + pista + CTA).
- Export de career-plan queda deshabilitado sin plan, acciones en una fila.
- stats: fuera el encabezado duplicado «Market report» y el placeholder de región ya cabe (texto más corto ×18, ancho fijo).
- Config ya no filtra rutas absolutas —
displayPath()del servidor muestra~/…o el nombre del archivo. - El helper de LLM_PROVIDER son tres líneas cortas (×18 +
pre-line), no un muro mono de 150 caracteres. - La tarjeta EMAIL del perfil mantiene la dirección en una línea (puntos suspensivos; salto solo ≤480px).
- El texto de Health ya no parte palabras («Run buttons» entero; overflow-wrap anywhere).
- El código en línea de help sobrevive en RTL (aislado LTR, nowrap, desplazable).
- La píldora «Live evals» del hero ya no colisiona en ru/ar (flex-wrap, iconos no reducibles).
Añadido
- Preajuste de mosaicos oscuros (
MAP_TILE_DARK_URL+ attribution) documentado en código; la ampliación del CSPimg-srccubre el host de Carto.
Notas
- El modo oscuro por defecto es una inversión CSS de OSM: Carto sin clave cerró sus mapas tras una API key (verificado), así que el preajuste con clave es opcional, no el defecto.
[1.246.0] — 2026-10-09
Los diez hallazgos principales del barrido de diseño sénior CAR-48 están corregidos, incluidos el bloqueante y los siete mayores.
Corregido
- Apply oscuro: el enlace de la tarjeta de información vuelve a verse. La tarjeta mantenía su superficie clara en modo oscuro; el enlace «Need Playwright? See …» quedaba en ≈1.0:1.
.callout--infocon tokens por tema (surface/border/text/link) sitúa el enlace oscuro en ≈7.3:1. - RTL ya no invierte las composiciones numéricas. Las píldoras de puntuación («1.5 / 5»), las bandas de informes («≥ 4.5», «< 3.5») y el «/ 5.0» del panel reciben aislamiento direccional (spans LTR); el diccionario ar reformula los rangos numéricos con palabras donde la prosa lo pide.
- El markdown del CV permanece LTR. El textarea y la vista previa se renderizan con
dir="ltr"en árabe; el resto sigue RTL. - La columna DATE de informes no parte nunca más una fecha ISO (nowrap + cifras tabulares).
- La barra lateral termina sobre el HUD de uso (reserva del scrollport + desvanecido): el último elemento de navegación ya no queda debajo del panel; espejado para RTL.
- Escaneo móvil @390: «Save search» se ve completo: la fila de controles salta y se comprime en vez de recortarse en el borde.
- Los controles del mapa siguen el tema oscuro (zoom, capas, atribución). Los mosaicos no cambian a propósito: su origen está fijado por la directiva CSP
img-srcdel servidor. - El FAB de chat ya no tapa contenido interactivo: el padding de desplazamiento lo despeja (botones Disable de Portals, píldora de puntuación de Reports, entrada de two-pager, atribución de Leaflet).
- El registro de actividad habla el idioma de la interfaz: 22 slugs de acción localizados ×18 locales (con fallback al id crudo) y chips de filtro traducidos; la columna TARGET permanece (la llena auto-pipeline).
- Los formularios de assessments tienen etiquetas reales (frase normal, enlace
htmlFor/id) en lugar de solo marcadores.
Notas
- No en esta versión: mosaicos oscuros del mapa (requiere ampliar el CSP
img-src; cambio del servidor, no hecho en cliente a propósito); variantes oscuras de los callouts amarillos (config,batch): la mecánica de tokens ya queda lista.
[1.245.0] — 2026-10-09
El tamil (தமிழ்) se une como el 18.º idioma de la interfaz: el primer idioma nuevo desde la ola de 17, con paridad completa de claves.
Añadido
- Tamil (
ta): 1449/1449 claves con paridad completa respecto aen, sin marcadores perdidos, con las convenciones índicas-técnicas al estilo hi (proveedores, modelos, rutas y comandos permanecen en latino).detect()aceptata,ta-IN,ta-LK; el tamil es LTR (fijado por prueba). - docs/help/ta.md completo (33 H2 / 125 H3, recuentos de fuentes verificados contra el registro vivo, anclas canary de Hermes, YAML literal), más los espejos README.ta.md y CHANGELOG.ta.md.
- Puertas de enumeración 17 → 18: LANGS, pruebas de detect, auditoría i18n, paridad de changelogs, guarda RTL,
langsde CI — que también incorporahi, antes ausente de la lista.
Notas
- El landing de cvstart.org sigue en 17 locales por ahora — traducirlo es una tarea aparte; la sincronización de contenido del sitio ya publica docs/help/ta.md y el changelog ta.
- REPORT_LABELS de
report-header.jssigue en 17: el análisis de informes tamiles llegará con la generación de informes tamiles en el repo padre.
[1.244.2] — 2026-10-08
El pulido de la página de escaneo: el lanzador, la barra de estado, el panel de reposts y los filtros se rediseñan como un chrome consistente — y la tabla de resultados ya no puede ser estirada por datos del board.
Corregido
- Las celdas de salario muestran solo el rango monetario. Algunos boards ponen toda la descripción de beneficios en el campo del salario — la celda visible ahora muestra solo el tramo económico y mueve el texto al tooltip (antes: una celda de 6 líneas que estiraba cada fila).
- Las filas con título vacío muestran la empresa (y luego un guion neutro) en lugar de una celda gigante en blanco.
- El badge de seniority nunca se parte en su columna de ancho fijo.
- Las filas sin puntuación muestran un «◎ —» atenuado con tooltip explicando que la comparación con el dos-páginas no encontró coincidencias (antes: nada, lo que parecía roto junto a filas con puntuación).
Añadido
- Rediseño del chrome: tarjeta lanzadora (fila de controles alineada, botón primario dominante), barra de estado del terminal con punto de estado (reposo/ejecutando/listo/error) safe para reduced-motion, panel de reposts con tope de altura y cabecera sticky (un dataset de 1,560 clústeres renderizaba un panel de 155,000 px), y el bloque de filtros como cuadrícula responsiva uniforme con pie alineado.
Notas
- Todo lo demás en #/scan es idéntico a v1.244.1 — solo cambiaron el pulido del chrome y las cuatro correcciones anteriores. Tests 5045 → 5068 unit.
[1.244.1] — 2026-10-08
Corregido
- Scan icons announce real values. The boost/fit/score icons’ accessible names leaked the raw dict templates (“Fit: {band}”, “Boosted by {by}”) —
{band}/{by}/{score}are now substituted (band word, booster keyword, numeric score). [HIGH a11y, caught live in the v1.244.0 regression round] - Title pipes split correctly. A 4-segment title like «C++ | Rust | Go Developer | Onsite» no longer lifts “Go Developer” out as a “country” — the split demands the segment resolves to a real country (conservative
Countries.detectCountry). - Meta dates render for every row.
daysSinceaccepted date-only ISO, but the API serves full timestamps for ~83% of dated rows — those rows showed no date segment.
Notas
- Todo lo demás en #/scan es idéntico a v1.244.0 — solo cambiaron las tres correcciones anteriores.
- Deliberadamente no hecho: estado de filtros en el hash, navegación de filas por teclado y el parseo de
5 lakhs INR(pendientes previos).
[1.244.0] — 2026-10-08
Los resultados del escaneo se leen como una lista, no como un muro: una vacante = dos líneas, las señales son iconos, y un escaneo de 700 filas es una tabla paginada en vez de una página de 37,000 px.
Corregido
- Anatomía de la fila. Cada vacante se muestra como una fila acotada de dos líneas: línea 1 el título, línea 2
company · location · source · date · work-type. El badge de boost, la banda de encaje del título y la puntuación se convirtieron en iconos con nombres accesibles localizados y tooltips (los lectores de pantalla leen «Соответствие: сильное» / “Fit: strong”, no un muro de palabras); los badges de confianza y reubicación se plegaron en el mismo raíl. Seniority y salario quedan como columnas auxiliares solo de escritorio. - Higiene del título. Un segmento final
| Germany | Remotese separa del título hacia la línea de metadatos — consciente de Unicode, solo cuando el final es un marcador real de modalidad y el segmento medio tiene forma de lugar (las barras legítimas como “C++ | Rust | Go Developer” nunca se dividen). - Fin del desbordamiento horizontal. Layout de tabla fijo,
min-width: 0, título/metadatos de una línea con elipses y el texto completo en el tooltip: cero desbordamiento de página y cero scroll interno de tabla a 1440 px y 390 px (la página antigua desbordaba 675 px en móvil). - Paginación. Los resultados se muestran a 50 por página (25/50/100/200 seleccionables) — un escaneo de 700 filas mide ahora ~4,600 px en vez de ~37,000 px, y el DOM contiene ~1,000 nodos en lugar de 5,500+.
Añadido
- Pruebas de contrato de layout: la fila de dos líneas, los nombres accesibles de los iconos (en + ru), el presupuesto de nodos de paginación y el guardia de conteo/orden de title-fit corren en la suite del navegador — el rediseño no puede regresar en silencio.
Notas
- Tests 5036 → 5045 unit, la suite del navegador ganó los contratos de layout, cobertura media 98.14 % líneas / 89.34 % ramas (suelo 96/86).
- Lo que el escáner encuentra, las fórmulas de puntuación y
last-scan.jsonno se tocan — title-fit sigue siendo una anotación. - No está en esta release: estado de filtros en el hash de URL y navegación de filas por teclado (siguiente); el parseo de
5 lakhs INR(preexistente).
[1.243.2] — 2026-10-08
Corregido
- Versión desplegable cortada como v1.243.2. El tag git v1.243.1 fue re-apuntado después de publicarse, y el
git fetch --tagsdel servidor rechaza correctamente sobrescribir un tag existente — así que v1.243.1 nunca fue desplegable. Este tag lleva el árbol idéntico (el fix del listener window del modal en v1.243.0) más este incremento de versión. Nada más cambió; el tarball de npm 1.243.1 ya contenía el fix.
Notas
- El código de la aplicación es idéntico a v1.243.1 — solo la etiqueta desplegable es nueva (el
git fetch --tagsdel servidor rechazó la v1.243.1 re-apuntada). - Deliberadamente no hecho: forzar la actualización del tag en el servidor (nunca se mueve un tag publicado).
[1.243.1] — 2026-10-08
Corregido
- El modal «Registrar resultado» ya se cierra de verdad al navegar. El guardia de v1.243.0 escuchaba
hashchangeendocument, donde ese evento nunca se dispara — el arreglo llegó muerto y el modal seguía bloqueando la vista siguiente (detectado en producción por la ronda de regresión v1.243.1). El guardia escucha enwindow; la prueba también despacha allí y se verificó roja contra el código roto.
[1.243.0] — 2026-10-08
La capa del navegador protege tu trabajo y habla tu idioma: ninguna edición se pierde sin avisar, ningún fallo en silencio, ningún inglés residual en una interfaz traducida, contraste AA en todas partes.
Corregido
- Las ediciones sin guardar sobreviven a los re-renderizados. El editor de CV mantiene un búfer sucio y lo restaura en cada render — Cancelar, un cambio de idioma o navegar a CV Studio ahora avisan en lugar de descartar; el plan de carrera conserva su texto generado igualmente. Los fallos de carga en Config/CV Studio/career-plan/memory/two-pager muestran un error y deshabilitan Guardar en lugar de sobrescribir el archivo con blanks.
- El modal «Registrar resultado» ya no sobrevive a la navegación. Abrirlo en
#/trackery pulsar otra vista lo dejaba bloqueando la página entera (detectado en la regresión de dos entornos de v1.242.1) — ahora se cierra al cambiar de ruta; sus hermanos de scan también detienen sus flujos SSE y sueltan los listeners al navegar. - El análisis de salarios y palabras clave lee todos los alfabetos. Los chips de palabras clave usan límites de palabra Unicode (cirílico,
c++,.netfiltran correctamente en vez de a cero filas); un parser de salario compartido arregla$182.9K - $240K,от 100 000 руб,100 тыс. руб.,50 000 Kč(era 50M USD) y la semántica de rangos ($100-150K); «Germany or Netherlands» obligatorio ya no cuenta como cumplido Y violado a la vez. - Los streams se asientan exactamente una vez. Una conexión SSE caída entregaba el error dos veces y podía dejar Generar deshabilitado para siempre; cada estado terminal (done/error/cierre/throw, incluido un proceso hijo muerto) asienta un único camino y reactiva el botón. El auto-pipeline vuelca su último fotograma bufferizado en lugar de descartarlo.
- Privacidad y corrección en los caminos pequeños. El cuerpo del bug-report ya no lleva la URL de la vacante evaluada ni slugs de informes; la privacidad del CV enmascara enlaces
linkedin.com/in/…desnudos, nombres EN MAYÚSCULAS y deja de destrozar fechas UE; la resolución de países toma la coincidencia más larga y se niega a adivinar cuando se nombran dos países («Cambridge, MA» ya no → UK); las descargas conservan nombres de archivo en cirílico. - Router y a11y. El skip link ya no cae en 404 como
ontent; el elemento de nav activo llevaaria-current="page"; un solo<main>/un solo<nav>; Escape cierra el drawer móvil (que ahora está realmente fuera del orden de tabulación cuando está cerrado);scroll-padding-topmantiene el foco visible tras las barras sticky; el banner sticky ya no tapa la topbar. - El contraste es AA en ambos temas — los toasts del tema oscuro eran blanco-sobre-blanco (1.10:1): toasts, botones, banners y enlaces sobre
--rauschahora resuelven mediante tokens invariantes del tema, protegidos por pruebas de contrato WCAG calculadas (≥ 4.5:1). - Todo lo demás de la ronda: evaluate muestra el aviso de truncamiento; memory/two-pager se niegan a Guardar sobre una carga fallida; scan deja de huérfanar su SSE; los resultados de scan clasifican los boards con API desde el registro del servidor en vez de una regex desfasada; «Proceed with Caution» del tracker es un aviso, no un badge rojo; las estadísticas pliegan estados localizados; los gráficos de barras se leen bien en RTL; el FAB de docs ya no cubre contenido en móviles;
GET /app.jsresponde 404 en vez del shell del índice;GET /api/runnerslista sus acciones.
Añadido
- El danés entra en la detección automática — un navegador
da-DKahora obtiene la interfaz en danés (el diccionario estaba completo, la detección lo ignoraba);zh-Hant*mapea a chino tradicional.cv-diagnostics— la lista de verificación del currículum — está completamente localizada (28 clavesdiag.*× 17). 26 etiquetas de field-spec añadidas; 31 claves muertas eliminadas; las listas de proveedores ya no afirman que Hermes es «el último del orden automático»; los aria-labels del docs-fab/menú están localizados.
Notas
- Tests 4812 → 5036 unit, navegador 118 + nuevos contratos de navegación/contraste, cobertura media 98.15 % líneas / 89.34 % ramas (suelo 96/86).
- No está en esta release: el rediseño de
#/scan(Fase 4 — v1.244.0, spec borrador); una clave dedicadaexport.docxFailed(el fallo reutilizacommon.error);5 lakhs INRsigue parseando como 5 (preexistente).
[1.242.1] — 2026-10-08
Corregido
- El archivo de la release vuelve a ir sin enlaces simbólicos de
node_modules. El tag git de v1.242.0 llevaba un symlinknode_modulesen la raíz ysite/node_modules(ungit add -Ageneral los coló sin superar los patrones de ignore con barra final), lo que rompió la extracción tar del despliegue en el servidor — el despliegue hizo rollback con seguridad y producción siguió en 1.241.1. Ambos enlaces están fuera del repositorio y los patrones ignore ampliados; el tarball de npm nunca se vio afectado (npm excluyenode_modulesal empaquetar).
Notas
- El código de la aplicación es idéntico a v1.242.0 — solo empaquetado. El contenido de corrección de fuentes llega a producción con este despliegue (v1.242.0 nunca llegó a producción; su despliegue en servidor se revirtió por el fallo de tar).
- Deliberadamente no hecho: re-etiquetar v1.242.0 (npm ya publicó 1.242.0; el tarball del registro está limpio — npm excluye
node_modulesal empaquetar).
[1.242.0] — 2026-10-07
La capa de datos del escáner vuelve a ser de fiar: las fuentes muertas están vivas, una respuesta malformada ya no puede hacerse pasar por un tablero vacío y una URL de tablero hostil ya no puede redirigir un escaneo.
Corregido
- Las fuentes muertas vuelven a la vida. justjoin (la envoltura de la API cambió a
{data, meta.next.cursor}) lee su catálogo completo mediante un recorrido por cursor con los nuevos campos de salario; nofluffjobs responde 400 sinsalaryCurrency— se envía la consulta obligatoria del padre y los resultados se paginan (pageTo). himalayas y jobicy solo veían las ~20 ofertas más recientes de catálogos de más de 100 000 — ambos recorren ahora sus catálogos completos (paginación por offset / cursor con topes de páginas). - Un 200 con la forma equivocada lanza un error en la página 1. Unas ~40 fuentes leían respuestas malformadas o de desafío como «0 ofertas» y el escaneo «tenía éxito» en silencio. Las nuevas guardas de
server/lib/sources/_shape.mjs(requireArray/requireContainer/requireObject) hacen el fallo ruidoso en todas las familias; un fallo en una página posterior conserva las páginas ya recogidas; la paginación se detiene según el recuento bruto de la página, no el filtrado. - SSRF: las comprobaciones de proveedor por subcadena se sustituyen por pines de host exacto analizados. lever (
clever.comcontienelever.co), greenhouse (5 hosts + los hosts heredados con slug de rutaboards[.eu].greenhouse.io), ashby, smartrecruiters, workable, gem REST, ibm, arbeitsagentur, hecklerkoch (solo www), workingnomads (http://169.254.169.254era alcanzable a pesar de la guarda DNS), remoteok, remotive, rss, successfactors.redirect:'error'en todas partes; las URLs de oferta deben serhttps:en el host fijado (se descartanjavascript:/data:/ las de fuera del host). - Una entrada de portals mal configurada ya no puede abortar todo un escaneo.
resolveAdaptercaptura los errores lanzados por los adaptadores,detectApise envuelve por empresa, los adaptadores que lanzan devuelvennull; los errores de fetch de comeet ocultan?token=antes de que lleguen a los logs o a los registros de cuarentena. - Paginación y frescura. workday recorre los tenants más allá de 100 ofertas (páginas por offset,
MAX_PAGES); telegram conserva los posts MÁS RECIENTES y pagina con?before=en lugar de quedarse silenciosamente con los más antiguos; tencent sigue avanzando cuando faltaCount; taleo analiza las tablas de encabezados en francés; avature se detiene según la longitud bruta de la página; marcas de fecha en UTC (amazon con desfase de un día al este de UTC, torre, workingnomads, trudvsem); entidades HTML decodificadas (geekjob, getmatch, hh); mycareersfuture emite la forma completa de oferta de 12 campos. - Detección de remoto endurecida. arbeitsagentur, vdab, rippling, teamtailor: el porcentaje de contrato («Verpleegkundige (100%)»,
bundesweit) y la palabra «distributed» («Distributed Systems Engineer») ya no etiquetan como Remote los roles presenciales.
Añadido
tests/adapters-pin-coverage.test.mjscompleta la cobertura de ramas de los pines de host que estos introdujeron.
Notas
- Tests 4419 → 4812, de navegador 118. Cobertura media 98,15 % de líneas / 89,36 % de ramas (suelo de la puerta 96 / 86).
- No incluido en esta versión: los hallazgos del cliente de la misma ronda de revisión (Fase 3 — vistas, libs, CSS/a11y, i18n);
sources/habr.mjssigue sin archivo de tests dedicado (exención de la línea base).
[1.241.1] — 2026-10-07
Las evaluaciones en vivo funcionan de principio a fin en todos los idiomas, el mapa de ofertas sitúa las ofertas largas con varias ubicaciones y un despliegue ya no mata un escaneo en curso.
Corregido
- Las evaluaciones ven el método completo. El presupuesto de contexto (144 KB) seguía cortando el final de
modes/oferta.mduna vez insertados el CV y el perfil, de modo que toda evaluación en vivo perdía sus últimos bloques. El presupuesto es ahora de 192 KB y el límite flexible del prompt de 256 KB, definidos una sola vez enllm-dispatch.mjs. - Informes en el idioma elegido. La instrucción de idioma ahora abre el prompt además de cerrarlo, y las palabras corrientes (rol, equipo, diseño…) se escriben en el idioma del informe; solo los nombres, las tecnologías y las citas literales siguen en inglés. Corrige los informes en hindi y japonés que salían casi enteros en inglés.
- Las evaluaciones lentas ya no acaban en HTTP 502. El plazo de la evaluación es de 300 s (antes el del proveedor por defecto, 180 s).
- Letras de bloque en hindi. Se reconoce un informe que escribe las letras A–G en devanagari (
## ए),## बी)…## जी)), ySCORE:acepta dígitos locales (३.८,٣٫٨,3.8) y coma decimal. - Mapa de ofertas: ubicaciones largas. Una celda como «Berlin Office · Berlin · Germany · Bosnia · …» (más de 300 caracteres) se rechazaba con HTTP 400 y la oferta se quedaba sin marcador. Ahora se usa el primer lugar real (
·,;,|,/, saltos de línea; se omiten los segmentos «Remote …», «(HQ)» y «Office»); solo se rechaza una consulta de más de 2000 caracteres. - Despliegue frente al escaneo horario. El despliegue retiene los temporizadores de escaneo y de autoevaluación, espera a que termine el que esté en curso (hasta 15 min), reinicia y siempre reanuda los temporizadores, también en el rollback.
Añadido
- Mapa de ofertas en cvstart.org en los 17 idiomas, y en los README en inglés y ruso.
- Capturas del mapa en todos los idiomas: una por idioma (
scripts/capture-map-screenshots.mjs), en los 17 README, en cvstart.org y en un nuevo apartado de la ayuda, «Cómo usar el mapa» (§33, ×17).
Notas
- Tests 4404 → 4419, de navegador 118. QA remoto (todas las rutas, el sitio, los enlaces y LLM en vivo × 17 idiomas) en verde en producción.
- No incluido en esta versión: evaluaciones en streaming; el límite de 300 s sigue siendo estricto (un modelo local lento aún puede alcanzarlo) y el puente de Hermes mantiene su llamada sin streaming.
[1.241.0] — 2026-10-07
Versión de endurecimiento a partir de una revisión de código con 28 agentes (~190 hallazgos verificados): se cierran las clases de seguridad, caída del proceso y pérdida de datos, además de un mapa de ofertas aportado por @bullitt186.
Añadido
- Mapa de ofertas (
#/map) — las ofertas del pipeline, del último escaneo y del tracker sobre un mapa de OpenStreetMap, geocodificadas mediante Nominatim con un User-Agent identificativo, una cola de 1 petición/s, retroceso exponencial y una caché en disco. Leaflet se sirve desde este servidor (sin CDN); la CSP solo incorpora el origen de las teselas enimg-src. Aportado por @bullitt186 (#381). - Guarda de peticiones — el servidor rechaza un
Hostque no sirve (DNS rebinding), así como las escrituras entre sitios y los GET con efectos secundarios (/api/stream/*,/api/run/*,/api/geocode). Un proxy inverso que reenvía un nombre público lo incluye enALLOWED_HOSTS; el workflow de despliegue escribe ese drop-in de systemd a partir de la URL pública. - Puerta de cobertura —
npm run test:coverage:gate(scripts/coverage-report.mjs) imprime la cobertura de líneas y ramas por archivo y falla por debajo de una línea base que solo sube.
Corregido
- Una petición mal formada ya no detiene el servidor. Express 4 ignoraba los manejadores asíncronos rechazados, de modo que un campo de tipo erróneo en
/api/deep,/api/auto-pipeline,/api/career-plan,/api/trackero un byte NUL en/api/statsterminaba el proceso; ahora son 400 en JSON, y cualquier otro rechazo se convierte en un 500. - Un escaneo abortado o fallido ya no borra los últimos resultados.
last-scan.jsonse conserva cuando se detiene un escaneo o todas las fuentes fallan, y un escaneo de una sola empresa fusiona en lugar de reemplazar. Una entrada mal configurada deportals.ymlya no aborta todo el escaneo. - Las evaluaciones ven el método completo. Los archivos de modos se cortaban a 16 KB, de modo que
oferta.md(92 KB) perdía los bloques C–G; los límites por archivo son ahora 128 KB para los modos y 64 KB para el CV, y se informa del recorte. Las respuestas vacías o cortadas fallan en lugar de escribir un informe vacío. - SSRF: los saltos de redirección se vuelven a comprobar contra destinos privados, se comprueba cada respuesta DNS, el plazo del escaneo cubre el cuerpo, las claves
*_BASE_URLsolo van al host del proveedor (Hermes/Ollama: loopback o LAN), pandoc se ejecuta con--sandbox. - Bloqueos del bucle de eventos: dos expresiones regulares cuadráticas (eliminación de HTML, saneado de markdown) sustituidas por escáneres lineales.
- Pérdida de datos: borrar una fila del pipeline conservaba la compensación de las demás;
$&en una URL corrompíapipeline.md; los guardados de entrevista simulada, networking e investigación profunda sobrescribían archivos anteriores; el historial de entrevistas conservaba los primeros 40 turnos en lugar de los últimos. - Capa LLM: el auto-pipeline y la investigación profunda respetan
LLM_PROVIDERy todos los proveedores mediante el despacho compartido, registran el uso e informan del truncamiento; evaluate/test de Gemini ya no guardan un informe; OpenAI usamax_completion_tokens, y unOPENAI_MODELsolo de Codex se envía comogpt-5; la descripción del puesto se delimita como datos no confiables. - Los PDF de informe, profundos e inline pasan
--skip-fact-check(la verificación de hechos del CV bloqueaba cualquier informe con cifras); el formato de página del perfil ya no se sobrescribe. - Escáneres: la cuarentena se indexa por URL (corregir una URL la reintenta), la deduplicación dentro de la ejecución por URL canónica, las entradas de lista en blanco ya no coinciden con todo, escrituras atómicas, las filas de historial
skipped_*no cuentan como «vistas». - Control de procesos: la escalada a SIGKILL nunca se disparaba; las ejecuciones por lotes matan todo su grupo de procesos al desconectarse.
- CI y publicación: la CI ejecuta ahora las puertas de paridad del changelog, de workflows y de i18n; permisos de workflow de mínimo privilegio; secretos de producción solo bajo el entorno
production; el paquete npm incluye ahoradocs/help/. - Tests: ningún test toca ya la red ni el proyecto padre real (un
CAREER_OPS_ROOTexplícito se respeta siempre); los algoritmos copiados se sustituyen por el código de producción.
Notas
- Tests 4056 → 4404, de navegador 118. Línea base de cobertura del servidor: líneas 96,7 %, ramas 86,3 % (
scripts/coverage-baseline.json). - Las especificaciones viven ahora en una sola carpeta,
docs/sdd/specs/; el plan del programa esdocs/sdd/PLAN.md, la matriz invariante→puertadocs/sdd/TRACEABILITY.md. - Nota para operadores: detrás de un proxy inverso fije
ALLOWED_HOSTS=<host público>; una URL base de Ollama/Hermes en un host público se rechaza ahora.
[1.240.0] — 2026-10-06
Paridad con el padre — career-ops main @ 62905981 (VERSION 1.35.0, 177 commits de upstream desde b39931e). Seis fuentes nuevas — 103 → 109 (104 EN + 5 RU) —, correcciones reflejadas en ocho proveedores existentes y el formato de lista de verificación del pipeline, aportado por @bullitt186.
Añadido
- Seis fuentes nuevas — 103 → 109 (104 EN + 5 RU), 98 → 104 adaptadores EN.
- ADP Workforce Now (
workforcenow.adp.comconcid+ccId) — paginación$skipsegún las filas realmente devueltas; una respuesta sinjobRequisitionsnimetalanza un error en lugar de leerse como un tablero vacío; enriquecimiento de detalle opcional (adpWorkforcenow.fetchDetails, 25 por defecto, tope de 100). - Gupy (
employability-portal.gupy.io) — barrido por palabra clave en todo el tablero, de la más reciente a la más antigua, con parada anticipada segúnsince_days;workplaceTypese lee como la cadena singular en pt-BR para no perder las ofertas híbridas; las páginas de empleo confidenciales se descartan. - JazzHR (
*.applytojob.com) — parser de tarjetas con respaldo de ancla desnuda, de modo que un rediseño parcial siga produciendo ofertas; unos enlaces de oferta sin ninguna tarjeta analizada lanzan un error, y un tablero realmente vacío devuelve[]. - Startup Jobs (
startup.jobs/feeds/jobs) — RSS, una sola petición; un cuerpo malformado, HTML o truncado lanza un error en lugar de devolver[](la corrección de revisión posterior a la fusión del padre,08fe5d06); CDATA y comentarios se enmascaran antes de buscar los items. - Taleo (
<tenant>.taleo.net, se rechazatre.taleo.net) — GET de la página shell y luego POST asearchjobs; una página shell sin id de portal lanza un error como tablero privado; las columnas se emparejan por encabezado. - UKG Pro / UltiPro (
recruiting[N].ultipro.{com,ca}) —Top=50+Skip,totalCountnunca se toma como condición de parada, tope de 100 páginas (acotado a 1500), páginas de detalle opcionales analizadas con un recorrido de llaves consciente de las cadenas.
- ADP Workforce Now (
Corregido
- Pipeline en el formato de lista de verificación del padre — el career-ops upstream escribe
data/pipeline.mdcomo secciones## Pending/## Processedcon filas- [ ] url | …, que la UI leía como 0 pendientes y en las que escribía añadiendo un bloque de código delimitado al final del archivo, donde el padre nunca mira.parsePipeline,addPipelineUrlyremovePipelineUrlahora leen y escriben esa forma (deduplicación también contra las filas procesadas; compensación como segmento etiquetadonote: comp …); el formato con bloque delimitado no cambia. Aportado por @bullitt186 (#380). - País incorporado a la ubicación — Ashby (
address.postalAddressprincipal), Breezy (country.name) y Recruitee (locationplana) añaden el país salvo que el nombre ya lo contenga como palabra completa, de modo que un rol remoto con sede principal en el Reino Unido y secundaria en EE. UU. ya no se descarta por un filtro de ubicación. - MokaHR — se acepta el host regional
hire-r1.mokahr.com(la lista blanca es exactamenteapp.mokahr.com+hire-r1.mokahr.com), el POST a la API y los enlaces de oferta usan el host propio del tablero, y unsuccess:falsedentro de la respuesta ahora lanza un error en lugar de leerse como un tablero vacío. - Avature — se analizan las tarjetas
article--jobs(el título sale del ancla de la cabecera, la ubicación delspanicon-address). - SAP SuccessFactors — una URL de búsqueda guardada
/go/<Category>/<id>[/<offset>]/se resuelve a la raíz del tenant; antes llegaba a…/tile-search-results/, que responde 200 con cero tarjetas. - Deutsche Bahn — db.jobs deja en blanco cualquier página de resultados que contenga una oferta que no sabe renderizar; el recorrido ahora ordena por
pubExternalDate_tdtcon 1000 por página (5 páginas como máximo), se detiene según el recuento bruto de anclas de resultado y lanza un error ante un encabezado de resultados ausente o enlaces de oferta sin analizar, en lugar de devolver un tablero incompleto. - HigherEdJobs — un desafío de Incapsula respondido con HTTP 200 se analizaba como cero items y se leía como un tablero vacío; ahora lanza un error.
- Un test leía el proyecto padre real en la máquina de un desarrollador —
tests/reports-list-cache.test.mjsfijabaCAREER_OPS_ROOTa un directorio temporal sincv.md/portals.yml, quepaths.mjsignora, así que en local contaba los informes del padre (6 en lugar de 3) mientras que CI, sin ningún padre al lado, pasaba.
Notas
- Tests 3782 → 4056. Suites nuevas:
sources-adp-workforcenow,sources-gupy,sources-jazzhr,sources-startup-jobs,sources-taleo,sources-ultipro,sources-location-country-fold,sources-deutschebahn-blanked-page,sources-higheredjobs-challenge-page, másparsers-pipeline-checklistdel #380. - Padre: el fork se llevó a
mainde upstream (144 → 0 commits por detrás) con las cuatro divergencias del ADR-0002 verificadas intactas; la reversión upstream delLOCATIONISH_REcirílico no llegó. - Ya publicado en la v1.239.0, no se repite: AppliTrack, NEOGOV, Red Rover, SchoolSpring, OCC Mundial, los tableros embebidos de Greenhouse, Workday
myworkdaysite.com.
[1.239.4] — 2026-09-29
Parche tras la regresión en vivo de la v1.239.3.
Corregido
- Las evaluaciones en coreano y chino tradicional indicaban que faltaba el resumen de puntuación. Esos informes llegan a 10–11 mil caracteres y el resumen va al final, así que quedaba más allá del límite de 8192 tokens de salida. Las evaluaciones (y el pipeline automático) admiten ahora 16384 tokens y, si un proveedor aun así corta la respuesta, el primer aviso lo dice en lugar de listar bloques ausentes.
- La comprobación en vivo tomaba una respuesta corta en alemán como no alemana (3 palabras vacías alemanas, ninguna inglesa). Una respuesta en forma de lista puede tener pocas; sin ninguna inglesa, ahora bastan tres.
- Tests 3778 → 3782.
Notas
- Las rutas de investigación profunda y de modos mantienen su límite de 8192 tokens: sus respuestas no llegan a él en las ejecuciones en vivo.
[1.239.3] — 2026-09-29
Parche tras la regresión en vivo de la v1.239.2.
Corregido
- Las evaluaciones en árabe seguían fallando la comprobación A–G después de que la v1.239.2 arreglara japonés e hindi. Un esquema enmascarado de los encabezados (sin texto del informe en el log) mostró la causa: el informe árabe escribe la letra de bloque en árabe, en orden abjad, diga lo que diga el prompt. La comprobación acepta ahora أ ب ج د هـ و ز como A–G cuando aparecen solas al inicio de un encabezado, igual que ya aceptaba los dobles cirílicos; las mismas letras dentro de una palabra árabe no cuentan.
- La comprobación en vivo imprime ese esquema enmascarado cuando falla la comprobación A–G, para diagnosticar el siguiente caso desde el log.
- Tests 3776 → 3778.
Notas
- La nota de la v1.239.2 decía que la comprobación no se relajaría; para el árabe ahora sí, porque tres ejecuciones en vivo mostraron que el prompt por sí solo no mantiene la letra latina.
[1.239.2] — 2026-09-29
Parche tras la regresión en vivo de la v1.239.1.
Corregido
- Una evaluación en árabe tradujo sus letras de bloque y una en japonés omitió el resumen de puntuación. La v1.239.1 solo repetía el idioma en la última línea del prompt. Esa línea ahora indica también que la letra latina A–G de cada encabezado y el bloque
---SCORE_SUMMARY---(marcadores y claves en inglés) no se traducen, y pide la escritura propia del idioma. - Tests 3775 → 3776.
Notas
- La comprobación del informe no se relaja para aceptar letras o marcadores traducidos: es el prompt el que mantiene estable el formato.
[1.239.1] — 2026-09-29
Parche tras la regresión en vivo de la v1.239.0.
Corregido
- Las evaluaciones en japonés y ucraniano salían casi todas en inglés. La instrucción de idioma estaba al principio del prompt, antes de decenas de kilobytes de archivos del proyecto y de la oferta en inglés. Ahora el prompt repite el idioma en su última línea.
- La comprobación en vivo marcaba como fallido un escaneo cuando el servidor ya estaba ejecutando otro (el escaneo horario, reintentado justo después del despliegue). Que el servidor rechace un segundo escaneo es correcto; la comprobación ahora espera y reintenta, hasta 30 minutos.
- Tests 3774 → 3775.
Notas
- El escaneo único del servidor no se relaja: un segundo escaneo sigue rechazándose con
SCAN_BUSY. Solo cambió la comprobación.
[1.239.0] — 2026-09-29
Paridad con career-ops @ b39931e: cinco fuentes nuevas y tres correcciones replicadas.
Añadido
- Cinco fuentes nuevas (98 → 103; adaptadores en inglés 93 → 98): AppliTrack (
applitrack.com/<distrito>), NEOGOV (SchoolJobs / GovernmentJobs), Red Rover (jobs.redroverk12.com/org/<id>) y SchoolSpring (<distrito>.schoolspring.com) para empleo escolar y del sector público, y OCC Mundial, la mayor bolsa de empleo de México (provider: occexplícito con consultas de búsqueda). Cada una está fijada a su host, solo usa HTTPS y rechaza redirecciones. - Tests 3500 → 3774.
Corregido
- Los tableros embebidos de Greenhouse (
job-boards.greenhouse.io/embed/job_board?for=<tablero>) apuntaban a un tablero llamado “embed” y fallaban; ahora el tablero sale de?for=, y solo en un host real de Greenhouse. - Los tableros de Workday en
myworkdaysite.com(/recruiting/<tenant>/<sitio>) no se reconocían; ahora se escanean, con los enlaces de las ofertas en el mismo host. - Un nombre de informe con paréntesis (
042-acme-(berlin)-….md) se cortaba en el primer)en el tracker; ahora se conserva completo.
Notas
- Los cambios del padre que solo afectan a la CLI no se portan — plantillas de PDF y CV, el scaffolder, el ejecutor por lotes, la importación de career-profile, la cobertura de palabras clave ATS, las traducciones de los modos de entrevista y el host CLI
pi: web-ui no los ejecuta. - Abrir ese informe desde el tracker aún requiere que la ruta de informes acepte paréntesis en el nombre del archivo; esa ruta no cambia en esta versión.
[1.238.3] — 2026-09-27
Las páginas siguen respondiendo durante un escaneo y los registros de QA remota ya no filtran el host de producción.
Corregido
- El panel y los informes tardaban segundos en cargar y agotaban el tiempo durante un escaneo.
/api/reportsy/api/dashboardleían y analizaban todos los informes en cada petición: unos 3,7 s en el servidor de producción, bloqueando cualquier otra petición; con un escaneo en marcha, las cargas llegaban al límite de 30 s. Ahora las cabeceras analizadas se guardan en caché por archivo y se renuevan cuando el archivo cambia, así que listar cuesta una comprobación por informe (500 informes: 430 ms → 3 ms en local). - Un hallazgo de la QA remota podía imprimir parte del host de producción en un registro público. GitHub solo oculta un secreto cuando aparece completo, y un mensaje cortado a 180 caracteres terminaba a mitad del host. Ahora los hallazgos y los informes completos se censuran antes de cortarse.
- Todas las evaluaciones con IA se marcaban como mal formadas. El prompt nunca pedía el bloque
---SCORE_SUMMARY---que exige la comprobación del informe (los scripts de evaluación del proyecto padre sí lo piden), y un informe traducido traducía la palabra “Block”, así que la comprobación no encontraba sus secciones. Ahora el prompt pide el resumen y la letra del bloque en cada encabezado, la comprobación acepta una palabra traducida antes de la letra y el bloque de resumen se elimina antes de mostrar o guardar el informe.
Añadido
- Tests 3488 → 3500.
Notas
- El escáner sigue ejecutándose dentro del proceso del servidor; esta versión elimina el otro gran bloqueo, pero no mueve el escaneo a un worker.
[1.238.2] — 2026-09-27
Corrección de los 502 que el servidor de producción aún mostraba durante el escaneo horario.
Corregido
- Páginas y recursos devolvían 502 durante un escaneo. Node cierra una conexión keep-alive inactiva a los 5 s; Caddy reutiliza conexiones inactivas hasta 2 minutos. Mientras un escaneo dentro del proceso mantenía ocupado el servidor, Node cerraba sockets por los que Caddy acababa de enviar una petición, y Caddy respondía 502 connection reset by peer (62 veces en un escaneo, con el servidor sano). Ahora el servidor mantiene las conexiones inactivas 125 s —más que el proxy—, así que el proxy siempre cierra primero.
Añadido
deploy.yml→verifymuestra por qué el proxy respondió 502 (tipos de error, direcciones ocultas), los 502 por minuto desde el último reinicio y el resultado del escaneo horario.release.ymlpuede crear una etiqueta que falta al lanzarse a mano, para publicar una versión donde no se pueden subir etiquetas.- Tests 3486 → 3488.
Notas
- El escaneo sigue ejecutándose dentro del proceso del servidor y puede ocuparlo durante segundos; moverlo a un worker no forma parte de este parche.
[1.238.1] — 2026-09-27
Parche de estabilidad tras la primera regresión en vivo del servidor de producción: un escaneo a la vez y arreglos del sitio.
Corregido
- Dos escaneos simultáneos tumbaban el servidor. Cada
GET /api/stream/scanejecutaba el escáner completo dentro del proceso sin ninguna protección; un escaneo lanzado desde la UI coincidió con el temporizador horario y el proceso cayó con JavaScript heap out of memory (dos veces, con errores 502 mientras tanto). Ahora hay un solo escaneo a la vez: una segunda petición recibe unerrorSSE con códigoSCAN_BUSYy no arranca nada. El flujo de despliegue además sube el límite de memoria del servidor (heap_mb, 448 MB por defecto). /helpy/changelogde cvstart.org se desplazaban de lado en móviles (y/changelogen ja/zh en escritorio): el código largo y el índice ahora se ajustan.- Faltaba el hindi en las alternativas de idioma del sitemap; la comprobación i18n del sitio ahora compara el sitemap con el registro de idiomas.
- La Ayuda §19 pedía añadir una clave a «los 8» archivos de idioma: son 17.
Añadido
- Regresión remota (
.github/workflows/remote-qa.yml): cada ruta × cada idioma de la app desplegada (solo lectura), cada página × idioma × móvil/escritorio de cvstart.org, cada enlace de los README y la wiki y, a petición, un escaneo real y llamadas de IA en vivo en cada idioma. - Flujo de despliegue del servidor (
.github/workflows/deploy.yml):recon,deploy(con reversión exacta) yverify(prueba de humo e informe de estabilidad). - Tests 3481 → 3486.
Notas
- El consumo de memoria del propio escáner no se reduce en esta versión: la protección impide dos escaneos a la vez y el drop-in sube el límite. Perfilar un escaneo real de 98 fuentes requiere la red de producción.
- El temporizador horario
career-ops-scanno cambia;reconahora muestra qué comandos ejecuta.
[1.238.0] — 2026-09-27
Paridad con el padre — career-ops main @ 993085ce (VERSION 1.34.0, 182 commits desde de2224a9). Cuatro fuentes nuevas, diez correcciones de proveedores reflejadas, una brecha SSRF a nivel de todo el escáner cerrada, y una pista gratuita de ajuste de título en los resultados del escaneo.
Añadido
- Cuatro fuentes nuevas — 94 → 98 (93 EN + 5 RU), 89 → 93 adaptadores EN.
- Eploy (
provider: eploy) — lee el sitemap/live-jobs.xmldel tenant en cualquier host de marca (solo HTTPS; se rechazan literales IP,localhost,*.local,*.internaly hosts de una sola etiqueta). Una respuesta que no es un sitemap (una página de login) lanza un error en lugar de pasar como cero empleos. Las páginas de detalle son opcionales (eploy: { fetchDetails, detailLimit }). - HiringRoom (
*.hiringroom.com) — primero JSON-LD/portal/jobs, con las tarjetas legadas de/jobscomo respaldo; las URLs de publicación enhttp://dentro del payload se reconstruyen en HTTPS. - PeopleSoft Candidate Gateway — detectado por la ruta
/psc/{site}/EMPLOYEE/HRMS/c/HRS_HRAM_FL.HRS_CG_SEARCH_FL.GBLen cualquier host de marca. Sigue por sí mismo las redirecciones de sesión del mismo origen de PeopleSoft (máximo 10 saltos, cada salto re-anclado al origen del tenant y comprobado contra direcciones privadas), reproduce el formulario “cargar más” y marca un recorrido parcial (peoplesoftIncomplete) en lugar de reportarlo como completo. - PrevueAPS (
*.prevueaps.ca) — resuelve el id del sitio desde la página de empleos y luego lee la API JSON.
- Eploy (
- Pista de ajuste de título en las filas de
#/scan. Cada resultado lleva una insignia aproximada de ajuste fuerte / relacionado / débil que compara el título de la publicación con los roles objetivo enconfig/profile.yml(target_roles.primary+ nombres de arquetipo). Es una estimación gratuita basada en palabras clave: nunca filtra, reordena ni cambia los recuentos, y el tooltip lo indica. Se calcula al servir los resultados, así que una edición del perfil se refleja en el siguiente refresco. Traducido ×17. Refleja el #3261 del padre.
Corregido
- Seguridad — la protección contra DNS-rebinding nunca se ejecutaba en un escaneo real.
http-json.mjssolo comprobaba las direcciones resueltas cuando recibía elfetchglobal desnudo, pero ambos escáneres inyectanmakeTimeoutFetch(), así que todo escaneo se saltaba la protección. Una fuente apuntando a un hostname que resolvía a10.xo127.0.0.1se llegaba a solicitar. La protección ahora se ejecuta dentro del propiomakeTimeoutFetch, corriendo en paralelo contra el timeout del escaneo para que un resolver bloqueado no pueda colgar un escaneo. Encontrado en la revisión de esta versión. - Seguridad — las direcciones IPv6 que incrustan una dirección IPv4 no se trataban como privadas.
http://[::ffff:127.0.0.1]/(que el parser de URL reescribe como[::ffff:7f00:1]) pasabaisValidJobUrl. Tanto la forma punteada como la hexadecimal de::ffff:a.b.c.d/::a.b.c.dahora se clasifican por la dirección incrustada. Del_ip-guard.mjsdel padre. - Rippling pasa a la API de tablero v2 (
ats.rippling.com/api/v2/board/<slug>/jobs), con paginación (10 páginas por defecto, máximo 50), un bloqueo de host en los enlaces de empleo y validación de slug. Una URL de API pegada ahora produce su slug real en lugar deapi. - Recruitee — las ofertas multi-ubicación listan cada lugar (
A · B), se eliminan las publicaciones demo(Sample)y las filas sin título, y se conserva una oferta cuando alguna de sus dos URLs es válida. - Pinpoint — se eliminan las publicaciones demo sembradas (el vídeo demo duplicado).
- Canales de Telegram — una primera línea con varias
|ya no se lee comoTítulo | Empresa, y se reconocen más palabras de rol. ElLOCATIONISH_REcon soporte cirílico del fork queda intacto (ADR-0002). - Workday — un 422/401/403 en la primera página se sondea una vez; la página de mantenimiento o la redirección de caída de Workday marca el tablero como muerto (puesto en cuarentena en modo estricto). Las peticiones CXS y de detalle ahora rechazan redirecciones.
- BambooHR — un
/careers/listredirigido se triaja a través del feed público de embed: vacío significa un tablero activo sin vacantes, cualquier otra cosa es un tablero muerto. - Heckler & Koch / Rheinmetall — un
heckler-koch.com/rheinmetall.comdesnudo se reescribe awww.para que el transporte sin redirecciones aún lo alcance. Ashby rechaza redirecciones. beesite ritma las páginas con eldelay()cancelable. - Los filtros de título eliminan los acentos en ambos lados (escáneres EN y RU), así que
Développeures capturado por un negativodeveloppeur. - La comparación de empresa del cooldown entiende nombres no latinos y formas corporativas (marcadores CJK/coreanos, İ turca con punto). Los nombres CJK antes colapsaban a una clave vacía y nunca coincidían.
location_filter.strict: truefalla cerrado ante una ubicación vacía cuando hay una lista de permitidos o bloqueados configurada.- Liveness — nuevas comprobaciones de API pública para embeds de Greenhouse (
gh_jid), Arbeitsagentur y We Work Remotely; SmartRecruiters ahora decide por el flagactivede la publicación. - PeopleSoft — una acción de formulario relativa “cargar más” se resuelve junto a la página de búsqueda, no en la raíz del origen.
Notas
- Tests 3210 → 3481. Suites nuevas:
sources-eploy,sources-hiringroom,sources-peoplesoft,sources-prevueaps,title-fit,fetch-timeout-dns-guard,cooldown-company-match-unicode,liveness-api-more-rungs,ru-scanner-title-accent-fold. - No portado, con motivos: bordes de palabra Unicode en el filtro de ubicación (el filtro de web-ui es por subcadena, así que no hay borde que corregir — un port completo y deliberado del filtro de ubicación del padre queda como trabajo futuro); deduplicación de requisición por mismo título (web-ui deduplica solo por URL); avisos de republicación de agregadores, alcance de dominios en lista negra, recibos de cero-no-verificado y la migración de
scan-runs.tsv(web-ui no tiene ninguna de esas superficies); el split por facetas y las etiquetas de truncamiento de Workday (web-ui obtiene una página por tablero);enrichDatede BambooHR (no hay gancho de enriquecimiento de fecha); la refactorización del sleep de_http.mjsdel padre en unos ~20 proveedores (web-ui ya usa undelaycancelable compartido); updater, doctor, PDF, tracker/merge-tracker,validate-profile,discover-new-companies, la app Next.js deweb/, bots de GitHub — exclusivos de CLI o retransmitidos. - Mantenido deliberadamente distinto del padre: Heckler & Koch / Rheinmetall permanecen solo HTTPS (sin actualización
http→https); un tablero de Workday muerto en un escaneo normal sigue devolviendo[]en lugar de fallar el escaneo.
[1.237.1] — 2026-09-22
Parche de una revisión de QA externa sobre v1.237.0. La corrección de Jobstreet se confirmó desde fuera por primera vez: en los hosts AU, NZ, HK y MY, la forma antigua /id/job/<id> devuelve un 404 limpio y la nueva /job/<id> alcanza una ruta real protegida por Cloudflare; en id.jobstreet.com se cumple lo contrario. Los enlaces perdidos eran reales. Se encontraron dos defectos, ambos en lo que la propia v1.237.0 había añadido.
Corregido
La etiqueta del enlace al libro estaba localizada, pero su destino no. Los 17 locales de la SPA y los 17 locales de cvstart.org apuntaban el enlace de pie de página de Agentic Coding Design Patterns a la edición en inglés, mientras que la única etiqueta traducida — la rusa, «Паттерны агентного кодинга» — prometía un libro en ruso. Un nuevo aplicador data-i18n-href (con la misma forma que data-i18n-title, restringido a valores absolutos https://) y una clave footer.patternsUrl envían ru a la edición rusa y cualquier otro locale a la inglesa. La prueba de regresión afirma el invariante en ambas direcciones: un título traducido implica una URL localizada.
SEEK está migrando de hosts, y el transporte rechaza las redirecciones. www.seek.com.au → au.seek.com y www.seek.co.nz → nz.seek.com: las páginas de detalle ya devuelven 301, y ambos pares de hosts sirven hoy la API de búsqueda v5 con 200. Como http-json.mjs fija redirect:'error' (una defensa contra SSRF que vale la pena conservar), el día en que la ruta de la API empiece a redirigir, una entrada AU/NZ en el host antiguo fallaría — y con solo los hosts www. en la lista blanca, nadie podría migrar. au.seek.com y nz.seek.com están ahora en ALLOWED_JOBSTREET_HOSTS; los hosts antiguos se mantienen, así que la configuración existente y los enlaces ya construidos siguen funcionando. Los imitadores (au.seek.com.evil.com, au-seek.com) se siguen rechazando.
Wiki: la fila de Agentic Practices en el mapa de documentación de Home era la única de doce que no escapaba | dentro de [[…|…]], así que GitHub cortaba la celda — enlace muerto, descripción oculta. Un solo carácter.
Notas
Recuentos sin cambios en 94 fuentes (89 EN + 5 RU), 89 adaptadores. Pruebas 3201 → 3210 (+5 locale del enlace de pie de página, +4 hosts de SEEK).
No cerrable desde fuera, registrado en vez de reclamado: Oracle Cloud (Akamai bloqueó ambas sondas REST), el pin de Personio y appendWorkType (necesitan una entrada en portals.yml, y la UI no tiene forma de eliminar una), y los casos de cadenas de liveness (sin página controlable; 40 sondas en vivo dieron 12 en vivo, 28 no concluyentes, 0 expiraciones falsas).
Help ×17 ahora lista ambas formas de host para AU-Main y NZ-Main. Estructura de encabezados sin cambios en 32 H2 / 122 H3.
[1.237.0] — 2026-09-21
Paridad con el padre — career-ops main @ 93c4302b (VERSION 1.33.0, 75 commits extraídos de career-ops-hq/main). Ninguna fuente nueva esta vez: cuatro correcciones reflejadas, y dos de ellas perdían ofertas aquí, en silencio.
Corregido
Jobstreet / SEEK — cada oferta australiana, neozelandesa, singapurense, malasia y de Hong Kong enlazaba a una página muerta. parseJobstreetItem construía la URL de detalle como ${baseUrl}/id/job/${id} sin condición alguna. El segmento /id/ no es una ruta — es el prefijo de configuración regional indonesio, y solo existe en los hosts indonesios. Todo otro host de la plataforma SEEK responde 404 en /id/job/<id>. web-ui ya había añadido www.seek.com.au, www.seek.co.nz, sg.jobstreet.com, my.jobstreet.com y hk.jobsdb.com a ALLOWED_JOBSTREET_HOSTS, así que el escaneo devolvía esas ofertas encantado — cada una apuntando a un 404. La ruta ahora se decide según el host: ID_LOCALE_HOSTS (id.jobstreet.com, www.jobstreet.co.id, jobstreet.co.id) conserva /id/job/, todo lo demás recibe /job/, y un origen no analizable cae a la ruta común en vez de lanzar una excepción. Dos de nuestras propias pruebas habían codificado el fallo — afirmaban hk.jobsdb.com/id/job/… y www.jobstreet.com/id/job/… — y se corrigieron.
Oracle Cloud — una página corta a mitad de lista terminaba el recorrido y dejaba fuera la cola del tablón. La condición de parada era listLen === 0 || listLen < PAGE_SIZE, pero ORC sirve menos filas que el límite a mitad de lista porque algunas filas se filtran del lado del servidor. Medido en origen sobre American Express: TotalJobsCount 454, páginas de 200 / 199 / 54 — el 199 se leía como el final, y las últimas 54 ofertas, el 12% del tablón, nunca se recuperaban. Ahora una página vacía siempre termina el recorrido; cuando el tenant informa un total, la paginación continúa hasta offset + PAGE_SIZE >= total; solo un tenant sin total contra el que comparar termina en una página corta. Esto coincide con la convención más amplia de que una API «puede devolver menos resultados de los solicitados… incluso si no está al final de la colección» (Google AIP-158). hasMore se sigue ignorando deliberadamente — algunos tenants devuelven hasMore:false en cada página de un tablón de 7000 ofertas. Con las páginas cortas ya sin terminar el recorrido, MAX_PAGES es ahora el único respaldo contra un tenant que informa un total falso, así que queda cubierto por su propia prueba.
Liveness — “This role is closed” no se reconocía, y una expiración falsa es permanente. El patrón era /this job (listing )?is closed/i, que no contempla las formas igualmente comunes con role y position; en un tablón, medido en origen, 111 de 111 ofertas inciertas usaban “role”. Se amplió a /this (?:job|role|position)(?: listing)? is closed\b(?!-)/i. El \b(?!-) final es una guarda para adjetivos compuestos: \b por sí solo rechaza closedown pero aun así coincide con prosa real como “This role is closed-loop control of the platform”, y es la anticipación negativa la que lo impide.
Liveness — job expired ahora vive en un nivel blando que no puede vencer a un botón de Aplicar visible. HARD_EXPIRED_PATTERNS se comprueba antes de la prueba de control de postulación, así que cualquier cosa colocada ahí gana sobre una oferta viva. /\bjob expired\b/i disparaba en falso en cuatro formas reales de página viva — una entrada de carrusel “Similar jobs”, un chip de filtro “Hide job expired”, un FAQ de pie de página, y la prosa de closed-loop de arriba — porque el clasificador recibe el texto completo de la página. Ahora vive en SOFT_EXPIRED_PATTERNS, comprobado después del control de postulación y antes de la heurística de página de listado, devolviendo expired_body_soft; un simple aviso “JOB EXPIRED” sin control de postulación sigue expirando. Esta dirección importa porque un expired falso se escribe en el historial de escaneo como skipped_expired y luego filtra por deduplicación a una oferta real fuera de todos los escaneos posteriores, indefinidamente, a menos que se configure scan_history.recheck_after_days.
Añadido
Personio — un personio: <slug> explícito fija el tenant. Muchas empresas incrustan su tablón de Personio como un iframe en una página de empleo de marca propia, así que careers_url apunta al dominio propio de la empresa mientras el feed vive en <slug>.jobs.personio.de; sin un pin, esos tablones no resuelven a nada. El slug tiene el conjunto de caracteres restringido por una lista blanca anclada (/^[a-z0-9][a-z0-9-]{0,62}$/i) y la URL resultante sigue pasando por el análisis new URL() existente, la comprobación HTTPS y la puerta de host PERSONIO_HOST_RE — el pin nunca es una forma de alcanzar otro host. Un slug que lleve ., /, @, :, .., espacio en blanco, un - inicial, o que exceda el límite de 63 caracteres de una etiqueta DNS, cae al comportamiento actual de api/careers_url. A diferencia del padre, el regex de host de web-ui distingue mayúsculas de minúsculas, así que el pin se normaliza mediante new URL() — AcmeGroup resuelve a acmegroup.jobs.personio.de en vez de construir un host que su propia lista blanca luego rechazaría.
Jobstreet — appendWorkType opcional añade al título los tipos de jornada de la oferta como sufijo, p. ej. Strategy Consultant [Part time], para que los filtros de título y el triaje puedan ver el tipo de empleo en el momento del escaneo. Desactivado por defecto; un workTypes ausente o vacío no hace nada.
Notas
Los recuentos de fuentes y adaptadores no cambian: 94 fuentes (89 EN + 5 RU), 89 adaptadores EN. Esta versión solo mueve el recuento de pruebas, 3164 → 3201 (+13 jobstreet, +7 Oracle Cloud, +9 liveness, +8 Personio).
El padre eliminó providers/telegram.mjs, consolidando todo en telegram-channel. web-ui registra ambos como fuentes de cara al usuario, así que la eliminación no se sigue aquí: bajaría el registro de 94 a 93 y rompería en silencio a quien tenga telegram configurado. Revisar solo como una retirada deliberada y anunciada.
jd-skill-gap.mjs ganó una forma de encabezado sin contracción, “what we are looking for”, y un recorte de marcador en negrita. web-ui retransmite ese script en vez de reflejarlo, así que la corrección llega con el padre desplegado y no necesita ningún cambio aquí.
No se porta, con sus motivos: lib/latex-escape.mjs, browser-extract.mjs, openai-eval.mjs, update-system.mjs y el scaffolder (web-ui ni los refleja ni hace shell hacia ellos); el TUI del dashboard en Go y el formulario de configuración de web/ (superficies separadas); doctor.mjs y analyze-patterns.mjs (retransmisiones de solo lectura cuyo fail-soft absorbe el cambio).
La divergencia cirílica \p{L} del fork en providers/telegram-channel.mjs sobrevivió intacta a la fusión con upstream y se verificó byte a byte contra una copia previa a la fusión.
career-ops-ui v1.267.1 · page 2/9 · mirror of the project changelog for cvstart.org