Журнал изменений
GitHubКаждое значимое изменение, релиз за релизом. Тот же файл публикуется в репозитории и переведён на все 19 языков.
[1.260.0] — 2026-10-26
Состав укрепления по итогам ревью, восемь мержей (#491–#498) из раундов opus-5-5 и AI-ревью над флотом v1.259.0: нормализация зарплат, скоупинг дат отчётов, устойчивость геокода, сброс кэша бандла r2+r3, фикс переполнения deep — плюс два интерфейса v2 (pipeline, reports). Каждое исправление въехало через красно-зелёный fix-forward.
Исправлено
- Нормализация сравнения зарплат (CAR-143 rev-2): маркеры периода распознаются только рядом с той цифрой, к которой относятся — «6 month contract, $120k» больше не растягивается до годовой ×12, а присоединённые RU/ES-формы периода («за месяц» рядом с числом) побеждают случайные слова; семейство символов
$/¥распознаётся, но остаётся без маппинга, пока явный ISO-код не делит строку (доллары AUD/CA/SG больше не проводятся как USD, форма ¥ RMB получает явную оговорку вместо конвертации в JPY с промахом ~20×); голые числа 19xx/20xx — всегда годы; цифры диапазона наследуют период партнёра;minimumиtarget_rangeпрофиля никогда не склеиваются в один диапазон. - Скоупинг fixReportDate (CAR-144 rev-2): серверная коррекция даты применяется только к преамбуле до первого заголовка
##(отчёт с заголовком-H2 получает консервативный no-op — устаревшая дата приемлема, выдуманная — нет); внутри преамбулы: исправляется единственный кандидат, иначе единственный жирный**Date:**, иначе ничего (два жирных или два обычных кандидата — не переписывается ни один). Гейт PDF-подписей FIX-5 теперь читает реально собранный словарь — отсутствие локали бросает ошибку вместо тихого прохода на английском фолбэке. - Чип локации Хабра + устойчивость геокода (CAR-142 rev-2): мета-чип Хабра становится городом только при структурном свидетельстве (ссылка на фильтр по городу или иконка локации) — уведомления о релокации («Возможна релокация») и типы занятости никогда не маскируются под локации; кэш геокода теперь переживает рестарт (сброс на диск по
beforeExit/exit/SIGTERM+SIGINT с ре-рейзом сигнала, грязный флаг снимается только после того, как запись легла), завершение ограничено (защёлка exit-цикла, счётчик поколений, LRU по свежести), а исчерпанный бюджет геокода отвечает 503 + Retry-After вместо общего 502. - Сброс кэша бандла (CAR-145 r2+r3): оболочка ссылается на
/js/bundle.js?v=<content-hash>— immutable при совпадении, no-store при несовпадении — закрывая 5-минутное окно деплоя, где свежая оболочка сочеталась с закэшированным старым бандлом. Хэш и поток делят один файловый дескриптор (никаких рваных чтений); короткое чтение отвечает null вместо хэш-фолбэка по stat, способного отравить целый деплой;make-bundleпишет файл идентичности и компаньоны .br/.gz через temp-файл с точкой в начале имени + rename, так что убитая сборка не может ни отдать полузаписанные байты, ни оставить после себя temp-файл. - Переполнение deep (CAR-146): сохранённый бриф исследования, у которого одно лишь имя файла шире экрана телефона, больше не растягивает
#/deepдальше 390px — карточка ограничена своей строкой, заголовок переносится, дата остаётся на одной строке; обход узких вьюпортов теперь проходит и#/deepс намеренно сверхдлинной засеянной фикстурой (единственная находка двух прогонов remote-QA закрыта). - Укрепление Pipeline v2 (ревью CAR-116): удаления — с подтверждением и отменой (честный текст отмены), никакого выдумывания компании из хоста в карточках, идентификаторы строк FNV-1a, точные счётчики массовых операций и защита от повторного входа на них.
Добавлено
- Pipeline v2 (CAR-116): обогащённые карточки, сгруппированный вид и массовое отбраковывание с отменой; 17 новых ключей
pipe.*поставлены переведёнными во всех 19 словарях. - Reports v2 (CAR-118): живой поиск с фильтрами по полосе оценки/легитимности/компании/дате, сортируемые заголовки (
aria-sort), сохраняемый карточный вид с кнопками действий по полосам, гистограмма оценок с фильтрацией по клику и унифицированный рендеринг «Company — Role» / «x.x/5» (window.ReportOps); 31 ключrep2.*поставлен переведённым во всех 19 словарях.
Примечания
- Каждое исправление прошло раунд ревью с красно-зелёным доказательством; две HIGH-находки AI-ревью к первому r2 бандла (хэш по короткому чтению, атомарность компаньонов) закрыты в r3, а раунды CAR-143/CAR-144/CAR-142 каждый перепроверили открытые находки своего предшественника.
- Намеренно не сделано: кластерный вид карты остаётся за CAR-125 (в очереди); восстановление метаданных pipeline на стороне сервера — отдельный тикет; ретроспективных правок для контента, сохранённого в состояниях до rev-2 v1.259.0, нет.
- Тесты 5988 (+165). Браузерный набор: 152 успешно / 0 провалов / 1 opt-in пропуск. Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); 48 новых ключей i18n легли со своим fan-out ×19 внутри самих фичевых PR.
[1.259.0] — 2026-10-25
Второй состав агентного флота, шесть исправлений: даты отчётов фиксируются на стороне сервера, геокодер карты кэширует ответы и распознаёт русскоязычные города, справка ×18 цитирует локализованные подписи кнопок, фразы интерфейса перестают смешивать языки, сравнение зарплат считается на сервере — а JS-бандл поставляется предсжатым (холодная загрузка 3.1 MB → 598 KB).
Исправлено
- Выдуманные даты отчётов (CAR-101): промпт оценки теперь фиксирует сегодняшнюю дату на стороне сервера («поле Date: ОБЯЗАНО быть ровно
— скопируй дословно, ничего не выдумывай»), а fixReportDateпереписывает уползшую дату на сегодняшнюю в каждой точке приёма отчёта (ветка Anthropic, сабпроцесс Gemini, OpenAI-совместимый хвост, /api/deep, auto-pipeline) с предупреждением и записью в activity — это коррекция, а не отказ. 349/789 продовых отчётов несли выдуманные даты; у новых это исключено. - Геокодирование карты (CAR-105): кириллические названия городов в строках hh.ru/Хабра теперь доходят до геокодера; персистентный ограниченный кэш (
data/geocode-cache.json, подwithFileLock) гарантирует, что найденный однажды город больше никогда не пойдёт к провайдеру повторно; вызовы геокода бюджетируются на запрос, поэтому карта заполняется постепенно, а не блокируется на 13.9 с. - Смешение языков в UI (CAR-108): 23 утечек по ключам исправлены в 17 из 19 словарей (85 значений) — «все matching», «только remote», «Открыть Scan», воронка «Evaluated → Applied…» и другое стали полноценными локализованными фразами, переиспользующими собственный продуктовый словарь каждой локали.
i18n-no-latin-leaksрасширен таблицей фрагментов и пробой воронки (красный→зелёный). - Переворот вердикта по валюте (CAR-109): сравнение зарплат считается на сервере в
comp-compare.mjsи встраивается канонически (Candidate minimum: … per YEAR (raw: …)противJD offer: …); месячные ставки ×12, формы k/к/тыс/萬 и десятичная запятая нормализуются до сравнения; неуверенная конвертация сама по себе больше не может перевернуть вердикт (кейс zh-TW «€80–100k ниже минимума» мёртв). Модель больше никогда не умножает. - Подписи кнопок в справке ×18 (CAR-107): локализованные бандлы справки цитировали английские подписи («⚡ Run live», «Save», «Scan now»), которых UI больше не показывает — 263 замены в 18 локалях, выверенные по исходникам вью; ассистент справки наследует исправление.
Добавлено
- Предсжатый бандл + кэшируемая холодная загрузка (CAR-104):
make-bundle.mjsна этапе релиза создаётbundle.js.br(brotli q11) и.gz; middleware со скоупом бандла согласовывает br > gz сVary: Accept-Encoding,public, max-age=300, must-revalidateи ETag/304; с защитой свежести (компаньонный файл никогда не отдаётся старее основного бандла), прямые запросы артефактов получают 404, CSP байт-в-байт идентичен. Холодная загрузка: 3.1 MB → ~0.6 MB по сети (−80%); повторные визиты в течение 5 минут передают ≈0 байт.
Примечания
- Все исправления пришли из агентного флота с красно-зелёным доказательством (13 + 28 + 1 + 30 + 7 новых кейсов в их наборах тестов) и прошли раунды ревью (CodeQL #579/#580/#584 исправлены в коде — стаб-URL парсятся через
new URL().host, мёртвая строка промпта удалена). - Числа дозаполнения оценок v1.258.0 теперь зафиксированы в CAR-129 (
updated: 22,reportScoreless: 159). - Намеренно не сделано: нет ретроспективной правки дат у 349 исторических отчётов (дозаполнение дат было бы операцией с одобрения владельца), нет кластерного вида карты (CAR-125 остаётся в очереди за этим гео-фиксом).
- Тесты 5823 (+74). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет (CAR-108 правит только значения существующих ключей).
[1.258.0] — 2026-10-25
Шесть исправлений от агентного флота одним составом: счётчик компаний в #/scan больше не смешивает вселенные, углублённое исследование требует источников, ошибки форм подсвечивают само поле, состояния загрузки получают скелетоны и мгновенный h1, а оценки кэшируют свой 37k-токенный префикс промпта.
Исправлено
- Счётчик «Active companies» в #/scan (CAR-100): числитель считал различные компании по ВСЕМ строкам результатов (1554 на проде — один hh.ru даёт сотни), а знаменатель — отслеживаемые компании с API-доступом (~90) — две вселенные в одной дроби. Вариант (a): числитель теперь считает компании среди строк, которые сам API-скан отслеживаемых бордов и принёс (матч по
_entry ?? company; строки работодателей hh.ru/telegram выпадают); пре-скановый фолбэк переехал в ту же вселенную (он мог показывать «104/90»). - Выдумывание фактов в углублённом исследовании (CAR-110): промпт deep теперь несёт жёсткий контракт — каждый фактический тезис заканчивается маркером
[source: domain.com](не можешь назвать источник → не делай утверждение; выдуманный маркер хуже написанногоunknown), неизвестные факты записываются какunknown, а отчёт ОБЯЗАН заканчиваться секцией## Data freshness. Якоря свежести: сегодняшняя дата по UTC + опциональныйurlкомпании вPOST /api/deep(с ограничением размера, сервером никогда не запрашивается). - Ошибки форм подсвечивают поле (CAR-130): неудачный POST помечает первое поле формы
aria-invalid+.is-errorи фокусирует его (WCAG 3.3.1); снимается при правке. Подсказка сетевой ошибки учитывает деплой — loopback сохраняет подсказку с dev-командой, а развёрнутый origin получает «сервер не отвечает» вместо совета сbash web-ui/bin/start.md(у баннера подключения был тот же хардкод). - Состояния загрузки (CAR-112): #/map и #/tracker рендерят свой h1 мгновенно (цель фокуса роутера существовала только после прихода данных), а общий хелпер скелетонов (
Skeleton.rows, мерцающие строки aria-hidden внутри регионаrole="status", переиспользующегоcommon.loading) заменяет голый текст «Загрузка…» на трекере, в результатах скана и в оболочке карты;prefers-reduced-motionостанавливает пульсацию. - Кэширование промпта оценки (CAR-111):
buildEvaluationPromptPartsразбивает промпт (~37k токенов) на байт-стабильный префикс (CV + профиль + файлы режимов + инструкции) и хвост с JD; путь Anthropic отправляет[prefix + cache_control: ephemeral] + [JD]— префикс тарифицируется по ставке чтения из кэша (≈10× дешевле) на каждом вызове после первого. Отсутствующий или несовпадающий префикс откатывается (fail-open) к побайтово идентичному старому запросу; OpenAI-совместимая каскадная цепочка провайдеров не тронута (там кэширование на стороне сервера).
Примечания
- Все шесть исправлений пришли из агентного флота с красно-зелёным доказательством (5 + 7 + 7 + 7 + 8 новых кейсов) и раундами ревью; первые CodeQL-алерты CAR-111 (#579/#580) исправлены в коде — тестовый стаб парсит хост URL вместо substring-сравнения, а мёртвая строка полного промпта удалена.
- Дозаполнение оценок выполнено на проде (эндпоинт v1.256.0, с одобрения владельца):
updated: 22,missingReport: 0,reportScoreless: 159— 159 строк без оценки указывают на отчёты, чьи заголовки старше машинного блока или несут нераспарсиваемые форматы оценки; восстановимо только переоценкой или более широким проходом парсера (отмечено в CAR-129). - Намеренно не сделано: нет изменения nulls-last для колонки commute (уже существующая особенность
Infinityпри нисходящей сортировке), нет ретроспективного переценивания старых записей usage, нет подсветки в стиле.is-errorдля форм фильтров скана (CAR-130 ограничен общим хелпером отправки mode-page + подсказкой в api.js). - Тесты 5749 (+36). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет (подсказка CAR-130 переиспользует
conn.down; регион скелетонов переиспользуетcommon.loading).
[1.257.0] — 2026-10-25
Два исправления от агентного флота: встроенный ассистент справки понимает деванагари и тамильский, а страница #/usage показывает, сколько Hermes стоит на самом деле.
Исправлено
- Поиск в ассистенте справки для hi/ta (CAR-103): класс слов в ключевом токенизаторе
docs-assistantбыл[\p{L}\p{N}\u30fc]+— без комбинируемых знаков. Каждая матра деванагари (ा ै ्…) и каждый тамильский знак гласной (ே ை ்…) разрезали слово на обрывки; правило длины отбрасывало короткие обрывки (хинди-запрос матчил 0 секций), а более длинные слова обрубались в мусор, сталкивавшийся с посторонними секциями. Теперь класс включает\p{M}; CJK-путь биграмм не тронут. hi «मैं स्कैन कैसे चलाऊं?» и ta «ஸ்கேன் எப்படி இயக்குவது?» достают правильные секции из поставляемых бандлов справки. - Стоимость расхода для Hermes (CAR-106):
normalizeUsage()сжимал usage провайдера до{in, out}и молча выбрасывал переданную шлюзомcost—#/usageпоказывал $0.0000 на 9.5 M токенов Hermes (его строка в таблице цен — 0/0). Форма записи usage получает опциональное обратно совместимое полеcost?; агрегатор предпочитает стоимость, переданную провайдером, в каждой записи и откатывается к оценке из таблицы цен, только когда её нет; мусорные и отрицательные значения отбрасываются, никогда не выдумываются.
Примечания
- Оба исправления пришли из агентного флота с красно-зелёным доказательством (3 + 5 новых кейсов; 14/14 и 10/10 зелёные; соседние 78/78 и 61/61). Первые PR (#470/#471) несли устаревшую базу worktree, конфликтовавшую со squash-смерженной v1.256.0, — заменены чистыми cherry-pick’ами (#472/#473).
- Намеренно не сделано: исторические строки
data/llm-usage.jsonlретроспективно не переоцениваются (в них нет поля стоимости; выгоду получают только новые записи). - Тесты 5713 (+8: docs-assistant ×3, usage ×5). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет.
[1.256.0] — 2026-10-25
Качество данных трекера: роль больше не записывается как компания, борд вакансий больше не записывается как работодатель, строки без оценки перестают всплывать первыми — и одноразовый эндпоинт дозаполняет недостающие оценки из самих отчётов.
Добавлено
- Дозаполнение оценок (CAR-129):
POST /api/tracker/backfill-scoresзаполняет ячейку Score у строк, чей связанный отчёт несёт распарсиваемую оценку (181 оценённая строка имела отчёт, но не имела оценки). Оценённые строки, явные сентинелы (N/A), строки без отчёта и отчёты без оценки не трогаются вовсе; переписывается только ячейка Score, под файловой блокировкой; форма оценки валидируется в месте записи и нормализуется к конвенции трекераX.X/5; прогон идемпотентен и возвращает{ok, updated, missingReport, reportScoreless}.
Исправлено
- Роль — никогда не компания (CAR-128):
guessCompanyRoleсчитал ЛЕВУЮ сторону строки заголовка с тире компанией, но борды пишут оба порядка — «Senior Frontend Engineer - Grafana Labs» записывал роль КАК компанию (13 строк: Grafana, CircleCI…). Теперь эвристика ключевых слов роли с границами слов выбирает сторону роли, а тип занятости никогда не становится работодателем («Senior Engineer - Remote (EU)» оставляет работодателя пустым, а не «Remote (EU)»). - Борд вакансий — никогда не работодатель (CAR-127): фолбэк по слагу домена записывал строки hh.ru как «hh» (120 строк). Слаги
hh/habrпополняют список не-работодателей; вывод работодателя из середины предложения («в компании X», «X ищет») теперь срабатывает и для хостов бордов вакансий (hh.ru/kz/uz/by, habr.com) — там, где текст страницы называет настоящего работодателя. По пути найдена первопричина: ASCII\b/\wв тех шаблонах вывода вообще никогда не совпадали с кириллицей — молча мертвы с v1.248.4; их заменяют Unicode-lookbehind’ы и классы\p{L}. - Строки без оценки сортируются последними (CAR-129): сортировка трекера по оценке использовала сентинел
-1, поэтому при восходящей сортировке по умолчанию каждая строка без оценки всплывала раньше каждой оценённой. Теперь null’ы уходят в конец при обоих направлениях (ФИЛЬТР по оценке сохраняет свой-1— «строка без оценки не проходит фильтр ≥4» — верная семантика именно там).
Примечания
- Исторические строки этим релизом НЕ мигрируются: фиксы парсера применяются к новым оценкам; оценки восстанавливаются через эндпоинт дозаполнения, а вот 120 ячеек работодателя «hh» требуют переоценки, а не перезаписи.
- В релиз вошёл раунд ревью opus-5-5: неякорный шаблон «X ищет» выброшен до публикации (любая последовательность слов с заглавной буквы перед глаголом записывалась бы как работодатель — инвариант «ничего не выдумывать»), добавлены якоря начала предложения «В…», а писатель дозаполнения перепроверяет целевую ячейку (после ручных правок существуют дубли строк с одинаковым
#), сохраняет число колонок короткой строки, искажённой вручную, и останавливается на первой строчке вне таблицы. Точка внутри имени с точкой («Сбер. IT») неотличима от точки конца предложения для якоря «в компании» — записывается консервативный префикс («Сбер»); шаблон, заякоренный по глаголу, ловит полную форму. - Намеренно не сделано: компаратор не извлечён из
tracker.jsв юнит-тестируемую библиотеку (исходно-статический гейт остаётся; отмечено в CAR-129 как follow-up), нет изменения nulls-last для колонки commute (уже существующая особенностьInfinityпри нисходящей сортировке), нет миграции данных для строк «hh». - Тесты 5705 (+10: 5 identity-тестов в
auto-pipeline-jd-gate, 2 CAR-128, гейт сортировки CAR-129, 4 дозаполнения, включая hostile-path pin — минус заменённые кейсы v1.248.x). Источники сканера не тронуты (реестр 121 = 116 EN + 5 RU · 116 адаптеров); ключей i18n нет.
[1.255.2] — 2026-10-24
Три заметимых для пользователя исправления из живой регрессии и отчёта о продовом падении: краш рендера #/scan, слепая проверка языка для вьетнамского и повторяющаяся потеря блока сводки в zh-TW.
Исправлено
- Краш #/scan (CAR-99): регенерация счётных гейтов, начиная с v1.253.0, записывала литерал
FALLBACK_SOURCESс двойной запятой в конце каждого элемента — 120 элизионных дыр в массиве. Тогдаnew Set(FALLBACK_SOURCES.map((s) => s.value))во вью сканера держалundefined, аid.lengthклассификатора компаний бросалTypeError: Cannot read properties of undefined (reading 'length')на каждом рендере страницы (поймано logbuf’ом на проде). Дыры убраны, а новый гейт (4 спеки, проверен на красном) исполняет файл и падает на любой дыре, непригодном элементе илиundefinedв Set идентификаторов адаптеров — текстовый drift-гейт этот класс видеть не мог. - Слепая проверка языка vi (CAR-97): вьетнамский — латиница, и в таблице стоп-слов живой QA для него не было записи, поэтому «стоп-слова vi» считали английские вхождения и вьетнамский ответ читался как
report not in vi(живая регрессия: vi 2, en 2). Теперь у вьетнамского своя таблица из десяти служебных слов — чисто вьетнамский отчёт проходит, английский по-прежнему падает. - zh-TW терял блок SCORE_SUMMARY (CAR-98): закрывающее напоминание о языке говорило лишь, что машинный формат переводить нельзя, но никогда — что блок сводки обязан существовать; короткий отчёт zh-TW (5,983 символа) нёс блоки A–G, но целиком опускал этот блок (рецидив класса v1.239.2). Открывающая директива и закрывающее напоминание теперь явно говорят, что блок обязателен и без него отчёт невалиден.
Примечания
- Этот релиз намеренно не меняет ни один источник сканера, счётчик реестра (121 = 116 EN + 5 RU · 116 адаптеров) или проводку провайдеров — только исправления, поэтому счётные гейты не тронуты.
- Тесты 5695 (+9:
car99-fallback-holes×4,car97-vi-lang-check×4, инвариант промпта CAR-98 ×1). Браузер 143/143. Повторная живая проверка vi/zh-TW едет в постдеплойной регрессии.
[1.255.1] — 2026-10-24
Два раунда исправлений по ревью: ревью Opus вьетнамской локали (9 находок) и второй проход ревью по фиксам CAR-91/92 (5 must-fix) — оба закрыты красно-зелёными тестами.
Исправлено
- Раунд ревью vi-локали (продолжение CAR-88): хангыльный глиф (
신), протёкший вproject.subtitle/project.roleHint, исправлен (độ khớp tín hiệu), с гейтом, который падает на любой CJK/Kana-глиф в значении vi, отсутствующем в EN-словаре; шесть URL-адресов маркдауна с пробелами в docs/help/vi.md (what-is career-ops,apply for a job,set up playwrightи ещё три) приведены к дефисным реальным слагам гайдов, с гейтом «никаких пробелов в URL». - Границы заголовков оценки (CAR-92 r2 H1):
&,.,/,'теперь считаются словесными символами в левой границе блоковой буквы —## Q&A,## Plan B,## Ph.D. requirement,## E-commerceбольше не регистрируются как блоки A/B/D/E; голый терминатор конца строки принимается только после токена блокового слова (Block/блок/블록/…); ASCII-покинул классы словесных терминаторов, так что заголовкиE-commerce/ई-मेलпереживают разрезание. - Отчёты с украинской/русской нумерацией (CAR-92 r2 H2): детектор последовательности А Б В Г Д Є Ж теперь общий хелпер; когда он находит 3+ последовательных буквы на уровне заголовка, этот уровень становится референсным уровнем для резака — буквенные подразделы
### A.больше не обрезают отчёт на блоке Б. - Жизненный цикл переносимых строк (CAR-92 r2 M1): строки, которые отказавший источник переносит в следующий скан, получают штамп
carriedSince, выбрасываются после 30 дней или 5 переносов подряд и добавляются после свежих строк — вместо того чтобы вечно тихо накапливаться. - Знаменатель + легитимность gemini-synthesis (CAR-92 r2 M2) и путь body-score сабпроцесса (M3): взяты из файла находок, исправлены красными тестами.
- Гейт дрейфа cold-load бандла обновлён под vi-словарь (90 script-тегов / 87 bundlable), неиспользуемая тестовая привязка удалена (CodeQL 572).
Примечания
- Формулировка счётчика адаптеров в баннере «Latest» README.vi, на которую указало ревью, живёт в баннере Предыдущий релиз и уже побайтово зеркалит EN README — оставлена без изменений для паритета EN↔vi (задокументировано в теле коммита).
- Тестовый пол: 5642 → 5686 (+26:
eval-validate-opus-r2,scanner-carryover-r2, vi-gate-проверки). Реестр без изменений: 121 источник (116 EN + 5 RU) · 116 адаптеров.
[1.255.0] — 2026-10-24
Вьетнамский (Tiếng Việt) становится 19-м языком интерфейса — словарь на 1525 ключей с полной паритетностью к EN, распознавание vi/vi-VN, полный комплект справки, зеркала README и CHANGELOG, интеграция в лендинг cvstart.org и 🇻🇳 в переключателе языка.
Добавлено
- Вьетнамский (
vi) — 1525/1525 ключей с полным паритетом кen;detect()принимаетviиvi-VN; вьетнамский — LTR (вRTL_LANGSне добавляется). ‘Pipeline’ остаётся ‘Pipeline’ — стандартное техническое заимствование, в белом списке. - Полный docs/help/vi.md (33 H2 / 125 H3), плюс зеркала README.vi.md и CHANGELOG.vi.md и вьетнамские строки-скаффолды в
prompts.mjs(LOCALE_NAMES+SCAFFOLD_STRINGS) — вывод LLM на вьетнамском работает из коробки. - Лендинг cvstart.org на вьетнамском —
site/src/i18n/vi.json, запись локали в sitemap и скриншотdashboard-vi.png; переключатель языка получает 🇻🇳 (19 позиций). - Перечисляющие гейты 18 → 19 — LANGS, пробы detect, i18n-аудит, паритет changelog, блок переводов README, locale-sweep — плюс red-first
tests/i18n-vi-locale.test.mjs(139 строк).
Примечания
- Лендинг cvstart.org получает vi — намеренное отступление от прецедента ta (тот релиз держал лендинг на 17 специально); задокументировано в спеке релиза.
- vi — латиница, поэтому гейт no-latin-leaks не применяется (он сторожит не-латинские локали); латинская техническая лексика здесь норма.
- Тестовый пол: 5634 → 5642 (1 пропуск = опциональный capture-тест раскладки в браузере, без изменений).
[1.254.0] — 2026-10-24
Релиз укрепления из раунда ревью максимальной моделью: снапшот сканера больше не теряет строки, одиннадцать инвариантных нарушений LLM-слоя исправлены, двадцать три SPA-must-fix въехали, и Paylocity ATS присоединяется к сканеру — реестр теперь поставляет 121 источник (116 EN + 5 RU) со 116 адаптерами.
Добавлено
- Paylocity ATS (родитель #5687): тенантные борды
recruiting.paylocity.com, мёртвый борд 302 → JobNotFound отображается в 404, чтобы карантин убирал запись, капmax_jobsс флагом усечения, безопасное формирование строк. Реестр: 121 источник = 116 EN + 5 RU · 116 адаптеров; гейты счётчиков перегенерированы.
Исправлено
- Безопасность снапшота сканера (R-12): скан, который не пытается ничего, теперь СОХРАНЯЕТ снапшот (
nothing scanned) вместо затирания; full-replace переносит старые строки отказавших источников — со штампом по энтри, чтобы отказавший «Lever» больше не держал чужие lever-строки — с повторным прогоном через ТЕКУЩИЕ фильтры и попаданием только вfiltered(уже-applied не всплывает как fresh). - Одиннадцать инвариантов LLM-слоя: языковая директива открывает И закрывает промпты deep/mode; Gemini-subprocess пересылает
--lang, не-нулевой выход → 502, не льёт stderr, держит 300 с и пишет usage; manual-fallback auto-pipeline несёт URL вакансии; truncation и warnings бандла всплывают в/api/mode; spend tier больше не обходится клиентом; дисконнект клиента абортит идущий вызов провайдера (сигнал проведён через всех); резак повторных заголовков больше не уничтожает валидные отчёты; экстракторы score/legitimacy согласованы с валидатором; украинская последовательность и арабская ва-лям tightened. - Раунд ревью Opus 5-5 НА самих фиксированиях (8 must-fix) затем въехал: мультибуквенная регистрация резака заголовков (один заголовок мог зарегистрировать букву из своего окна —
## G) Interview Q&Aрезал по G), расширенные терминаторы заголовков (**Block A** —,[A], полноширинные/、/CRLF), abort-проводка Gemini-subprocess, штампованная атрибуция перенос-строк, повторный прогон фильтров, уровень ссылочных заголовков по числу различных букв. - Двадцать три SPA-must-fix по sourcing-вьюхам (append однопейджного пейджера, клавиатура строк pipeline, восстановление фильтров saved-search, сбросы select’а source, откат дефолта в config, течи листенеров, close-on-terminal SSE, teardown scan-раннера, сырой fetch, «провал выглядит как empty», коллизия в window, сохранение фокуса) и assist-вьюхам (мёртвый help deep-link, течи help, повторное включение blank-overwrite в two-pager, single-flight платных прогонов batch/deep/mode, тихие концы стримов, вечные спиннеры на ошибках, призрачные строки проваленных фильтров, дубли docs-fab + язык).
Примечания
- Весь раунд прошёл ревью Opus 5-5 на максимальной строгости — порты до мержа (21 находка, 13 исправлено) и сами фиксы постфактум (8 must-fix, все въехали здесь с красно-зелёными тестами).
- Тестовый пол: 5262 → 5634 (372 новых спецификации по источникам, сканерам, LLM-слою и SPA).
[1.253.1] — 2026-10-24
Две HIGH-находки безопасности из ревью максимальной моделью исправлены: IPv6-литералы больше не обходят SSRF-гард сканера, а escape-regex CV-маркдауна больше не бэктрекает квадратично.
Исправлено
- IPv6-литералы обходили SSRF-гард (
http-json.mjsguardResolvedHost): скобочный IPv6-литерал ([::1],[::ffff:169.254.169.254],[fd00:ec2::254]— AWS IPv6 metadata) валилdns.lookupисключением, и гард fail-open пропускал fetch до cloud-metadata. Скобки снимаются, литерал классифицируется напрямую черезisIP+ классификатор private/loopback — без DNS и без fail-open. Найдено ревью Opus 5-5 (CAR-90). - ReDoS в escape-regex CV-маркдауна (
security.mjsstripDangerousMarkdown): паттерн-пояс с\s*/?\s*бэктрекал квадратично —<+ ~100k пробелов стоили ~16 с. Заменён на один плоский ограниченный класс (линейно; тот же вход теперь ~1 мс); всё, что старый паттерн экранировал, экранируется и новый. - tests/wiki-banner-check: протухшая версия JA-фикстуры (хвост #461) выровнена.
Примечания
- Оба фикса несут красно-зелёные регрессионные тесты (3 новых IPv6-кейса + ReDoS с временной границей; всего 5511 тестов, +7).
- Файлы источников сканера не затронуты — гард живёт в общем транспорте, через который ходят оба сканера.
[1.253.0] — 2026-10-24
Десять новых источников присоединяются к сканеру — Atlassian, BWI, Computrabajo, Dover, Google, Kalibrr, Magneto365, Platsbanken, Tech in Asia и Zoho Recruit — реестр теперь поставляет 120 источников (115 EN + 5 RU) со 115 адаптерами, а роль-матчер разводит заголовки, различающиеся только коротким капсом-кодом.
Добавлено
- 10 источников портированы из родителя 1.38.0, каждый со своим fetch-адаптером и CI-изолированным набором тестов (fake fetchImpl, без сети): Atlassian (единый auth-free JSON-фид, iCIMS-ссылки, allowlist https+icims.com на URL строки), BWI (JSON-LD ItemList, JSON.parse-first с escape-толерантным фолбэком), Computrabajo (LATAM, декод энтити через общую таблицу, испанское «remoto» читается как remote), Dover (потенантная DRF-пагинация, рендер
Remote (International), UUID-валидация бордов), Google Careers (первые страницы по фразам, работодатель карточки выигрывает, дедуп между запросами), Kalibrr (SEA-агрегатор, фид-ид как id строки — шлёп слага больше не плодит дубли), Magneto365 (LATAM, фиксированный межстраничный темп), Platsbanken (шведский JobTech API, ключевое слово = своё окно мимо потолка offset-2000), Tech in Asia (Algolia-ключи обнаруживаются заново из билда сайта, ссылка работодателя выигрывает при https и не-шаред), Zoho Recruit (скрытый-input борд, опциональный ограниченный detail-enrichment, парс валюты только ISO-4217). - Счётчики реестра: 120 источников = 115 EN + 5 RU · 115 адаптеров; пять гейтов счётчиков перегенерированы из живого реестра; фолбэк дропдауна
#/scan, ссылки источников cvstart.org и help §17 ×18 следуют за ними.
Исправлено
- Роль-матчер (#5688 порт): заголовки, различающиеся только коротким капсом-кодом («HR BP» vs «HR GP», «AWS» vs «GCP», даже в смешанных скриптах), больше не склеиваются фузз-матчем в одну вакансию; коды читаются из сырого заголовка с исключением noise-слов, гард стоит прямо перед коэффициентом Жаккара.
- Порты пережили ревью максимальной строгости Opus 5-5 (21 находка): throw на молчаливо-пустых бордах при дрейфе разметки (google/bwi), преабортированный скан кидает, а не возвращает
[], порядок decode-then-strip на LATAM-скреперах (XSS-форма strip-then-decode), https+allowlist на URL строк atlassian, host-ассерты с отказом портов, парс поздних страниц внутри try (партиалы выживают), честность тестов (бюджет запрошен точно, контракты коэрции законтрактованы правдиво). 13 находок исправлено, [spec]-хвост отслежен отдельно.
Примечания
- Пять RU-источников остаются web-ui-only (апстрим-адаптеров нет — так задумано); у каждого EN-источника есть адаптер.
- Coverage-рейтча: шесть самых больших портов подняты до ≥95 % branch (27 новых кейсов); защитный мёртвый код задокументирован пофайлово.
- Новые источники выбираются через
provider: <slug>в portals.yml — существующие конфигурации не затронуты, каноническое поведение не меняется (кроме счётчиков дропдауна/списков).
[1.252.1] — 2026-10-17
Баннер релиза в wiki теперь проверяется скриптом, а не ручным свипом — регресс v1.252.0 поймал протухший баннер только руками — и локальная директива явно переопределяет language.output профиля и язык вакансии.
Добавлено
scripts/wiki-banner-check.mjs— гейт, которого требовал регресс: читает каждыйHome*.mdв клоне wiki и сверяет баннер сpackage.jsonи с ЖИВЫМИ счётчиками реестра (scanner adapters из портального реестра — 105, а не протухшие 109 из баннера; route modules изserver/lib/routes/; UI locales из набора CHANGELOG). Regex баннера языко-независим по ФОРМЕ, а не по словам: локальные Home-страницы переводят и метку, и счётчики («Текущий релиз», «Aktuel udgivelse», «адаптеров сканера», «enhedstests»), и их числа уехали дальше (5177), чем EN-страница (5245).--tests Nпринимает число, которое только что напечатал свежийtest:ci;--check-onlyзапрещает неявный клон для офлайна. Офлайн-фикстурный тест —tests/wiki-banner-check.test.mjs.
Исправлено
- Баннер wiki Home ×18 возвращён в синхрон (уходит вместе с этим релизом): свип v1.252.0 менял бейджи и ссылки, но не строку баннера, и ничто механически на это не указало. Гейт выше делает это релизоблокирующей проверкой.
- Локальная директива задаёт приоритет языка (
buildLocaleDirective): «This UI locale overrides the profile’slanguage.outputsetting and the job posting’s own language». Собственное правило 6 родителя велит писать вlanguage.output— без фразы о приоритете модель разрешала два противоречащих указания непоследовательно по локалям, что и был разброс в языковой колонке v1.252.0. Закрепленоtests/locale-directive-language-output.test.mjsна тексте директивы (17 не-EN локалей + пустой EN).
Примечания
- Наблюдение за ФОРМОЙ ta evaluate (§3, смотреть): первый живой прогон v1.252.0 дал evaluate на 48 336 символов без Block G и SCORE_SUMMARY — похоже на зацикливание, а не на язык (CAR-70 — про язык). Повторится на следующем релизе — отдельная Linear-задача, не CAR-70.
- По FIX-промпту: родитель только для чтения; переопределение целиком живёт в директиве UI.
[1.252.0] — 2026-10-17
Родительский форк синхронизирован с career-ops-hq/career-ops — 264 апстрим-коммита, родитель 1.36.0 → 1.37.0 — с защитой обоих форк-дивергенций, а деплой переустанавливает работающего родителя на эту базу.
Добавлено
- Вьетнамский (vi) рыночный режим из апстрима (
modes/vi/,tuyen-dung/ung-tuyen): BHXH/BHYT/BHTN, thử việc, lương tháng 13, trợ cấp thôi việc — 21-й рынок в таблице режимов родителя. - Провайдер борда Tech in Asia (апстрим): читает публичный Algolia-индекс
job_postings, который сам запрашивает UI jobs.techinasia.com — SEA-борд стартапов, ~300 живых вакансий на замере, 273 из них индонезийские. Ключи обнаруживаются заново на каждом прогоне из билда сайта, собственная ссылка работодателя выигрывает у страницы борда, зарплата сознательно не attachится (поля зарплаты в индексе — флаги, а не деньги). - Восемь новых документов сигналов легитимности Block G (
modes/oferta-signals/08–17): несовпадение страны в терминологии льгот, несовпадение тега локации сторонней платформы с постингом работодателя, лицензирование агентств, иммиграционные требования с перехлёстом, запрещённый контент юрисдикции, ширина вилки pay-transparency, вопрос юристу о минимальной зарплате, раскрытие AI-скрининга, раскрытие срочного контракта, покупательная способность при релокации.
Изменено
- Укрепления на стороне родителя, которые едет база: проверка story-provenance работает по единицам и предложениям и не пускает цифры
user-cannot-confirmв переговоры, ATS и interview-вывод; fact-gate CV запускается на каждом рендерере и читает переформулированные и структурные заявления; LaTeX отвергает протухшие извлечённые слоты до записи; launchd-обход follow-ups пропускается, когда follow-ups выключены; update-system удерживает скан локальных правок в пределах argv. - Дивергенции форка пережили мерж:
providers/telegram.mjs(форк-единственный парсер, ADR-0001) и Unicode-lookupLOCATIONISH_REвtelegram-channel.mjs(ADR-0002). Документ бордов теперь перечисляет улучшенную строкуtelegram-channelапстрима, форк-единственную строкуtelegram, Tech in Asia и дефолтmax_pagesу Tencent, поднятый 20 → 100.
Примечания
- SPA не меняет поведение в этом релизе: это док-поезд плюс переустановка родителя. Деплой теперь работает на родителе 1.37.0; дельта родителя не трогает таблицу канонических статусов — статусы трекера рендерятся как раньше.
- В мерже апстрима разрешены два конфликта:
docs/SUPPORTED_JOB_BOARDS.md(обе телеграм-строки сохранены) иtest-all.mjs(walkTree-версияcopyDirSyncапстрима субсумирует форковский ручной guard вложенных чекаутов по.git; список импорта — объединение).
[1.251.1] — 2026-10-12
Фильтр специализаций матчится только по отличительным словам роли — старшинство и общие должностные существительные больше не матчат вакансию сами по себе — а remote-qa разделяет форму и язык, чтобы дрейф не краснил A–G.
Исправлено
- Фильтр специализаций выбрасывает старшинство и общие должностные существительные из матчера: «Senior PHP Developer» матчил «Senior Java Developer» через общий Senior/Developer. Матчер оставляет только отличительные токены (PHP, Go, Platform, SRE, Backend…); роль без отличительных токенов («Software Engineer») падает на матч всей фразой. Границы слов на месте: «Go» никогда не матчит «Golang» и «Google».
- remote-qa разделяет форму и язык: флаг A–G считается только по кодам блоков/SCORE_SUMMARY/looping; дрейф
report not in <locale>переезжает в языковой флаг и свою колонку warnings (в заголовке таблицы появилась недостающая 4-я колонка; коды сохраняются построчно). - Тулбар карты — честные 50/50 на 390 px (флекс 58/42 оставлял поиску 124 px и обрезал плейсхолдер), мёртвое CSS-правило
.job-map-job-selectудалено. - CodeQL 552 — неиспользуемая переменная
OVERLAYSв design-qa тесте удалена.
Примечания
- Мили по-прежнему отложены владельцем на следующий релиз.
- Языковой дрейф ta-оценки остаётся ЗАДОКУМЕНТИРОВАННЫМ ограничением бэкенда (hermes, доля прозы 2–26 % между прогонами; A–G держится, зацикливания нет): remote-qa теперь сообщает его как ожидаемую находку для ta, а не регресс. Двухшаговый вариант (оценка на английском + перевод) остаётся опцией, если понадобится.
- Список общих токенов намеренно минимален (старшинство + должностные существительные, en/ru): расширение — строка на язык, и каждое добавление рисует переефильтровать легитимную вакансию.
[1.251.0] — 2026-10-12
Селект на карте теперь фильтрует по СПЕЦИАЛИЗАЦИИ из настроек (config/profile.yml → target_roles.primary): выбор «Product Manager» или «Backend Engineer (Go / PHP)» оставляет на карте только вакансии, чей тайтл несёт слова этой роли. Локатор списком вакансий из v1.250.0 убран — это был неверный инструмент (правка владельца).
Добавлено
- Фильтр по специализации: селект на карте перечисляет специализации из настроек (target_roles.primary); выбор оставляет только вакансии, чей тайтл несёт одно из отличительных слов роли (по границам слов, без учёта регистра — «Go» не матчит «Google», кириллица работает через те же Unicode-lookaround’ы). Изменение фильтра сразу перерисовывает карту и чип статуса; Reset возвращает все специализации.
Изменено
- Локатор списком вакансий из v1.250.0 удалён: правка владельца — селект, перечисляющий отдельные вакансии, был неверным инструментом для карты; его заменяет фильтр по специализации. Пик-карточки, клики по маркерам и текстовый поиск остаются как были.
Примечания
- Мили все ещё откладывает владелец. Матчинг сознательно ТОЛЬКО по тайтлу словами из настроек: компании или описания затянули бы в специализацию нерелевантные вакансии. Вакансия вне всех настроенных ролей видима при «All».
[1.250.0] — 2026-10-12
Страница карты становится рабочим инструментом: локатор вакансий над картой наводит на любую видимую вакансию и открывает её пик-карточку (в том числе на десктопе), а текстовый поиск сужает карту по компании или должности — и то и другое переживает перезагрузку.
Добавлено
- Локатор вакансий: селект над картой перечисляет видимые (прошедшие фильтры) вакансии, отсортированные по компании+должности; выбор улетает к кружку — с зумом до разведения кластеров — и открывает пик-карточку. Пик открывается и на десктопе: смысл локатора — «покажи мне эту вакансию», навигация прочь его обесценила бы. Опции зеркалируют активные фильтры при каждой перерисовке — селект никогда не предлагает вакансию, которой нет на карте.
- Текстовый поиск: поле поиска сужает карту по подстроке компании+должности (сравнение без учёта регистра; в поле сохраняется введённый регистр). Запрос живёт в map-prefs между перезагрузками, Reset его очищает, опции локатора сужаются вместе с видимым набором.
Исправлено
- Docs-fab больше не перекрывает атрибуцию карты: с новым тулбаром карта уехала вниз, и нижний правый угол атрибуции Leaflet попал под плавающую кнопку. FAB теперь скрыт на
#/mapна всех ширинах (на мобильных так уже было); ассистент документации остаётся доступным со всех остальных страниц. - Два spec’а высоты мобильной карты сдвигают порог 600 → 540 px: тулбар законно занимает ~90 px вьюпорта, а ассерт проверяет «карта занимает большую часть вьюпорта», а не точное значение пикселей.
Примечания
- Мили (дорога в милях вместо минут) владелец отложил на следующий релиз.
- Локатор находит кружок через обратную ссылку
__job: первая попытка использовала_point, который L.Circle уже занимает — поиск молча проваливался и пик не открывался. Коллизия имени свойства с библиотекой невидима, пока фичу не пощупаешь. - 4 новых i18n-ключа входят во все 18 словарей, собранный снапшот перегенерирован.
[1.249.0] — 2026-10-11
Карта вакансий теперь ставит релевантность на первое место: спокойная шкала от бирюзового к серому выделяет лучшие вакансии, фильтры оценки и времени в пути сохраняются, на телефоне карта занимает доступную высоту и показывает карточку по первому касанию, а трекер использует те же фильтр и сортировку поездки.
[1.248.8] — 2026-10-11
Паритет с родителем: форк и web-ui синхронизированы со 115-коммитной дельтой апстрима — новый источник PageUp, URL-ячейки трекера парсятся как markdown-ссылки, стейт Assessment — плюс ta/ja языковая работа: зацикленные отчёты срезаются и называются, скелет заголовков держит A–G, а доля письма считается по прозе.
Добавлено
- Источник PageUp (
provider: pageup): скрапит vendor-hosted таблицы листинговcareers.pageuppeople.com— один тенант, одна запись, пагинация с правилами останова (нет свежих / короткая страница) и ограничениемmax_pages. В реестре 105 EN-адаптеров / 110 источников; PageUp появляется в фильтре источников#/scan(FALLBACK + живой реестр). Брендированный домен намеренно не клеймится — запись должна указывать на vendor host. - URL-ячейки трекера парсятся как markdown-ссылки (родитель #3516): родитель пишет ячейку
URLкак[ashby](https://…);parseApplicationsтеперь извлекает href (ключ дедупа) при разборе. Голые URL и заглушки проходят дословно. - Стейт Assessment (между Responded и Interview) приходит через живое чтение states.yml; встроенный fallback-список обновлён для паритета.
Исправлено
- Зацикленный отчёт срезается и называется, никогда не сохраняется молча (CAR-74): ta-отчёт в 46 587 символов повторял блок B и не доходил до D–G и оценки. Повторный заголовок блока теперь срезает отчёт на первом повторе, причина отказа называет луп (
report looping (block B repeated), …), а не-EN промпты несут готовый скелет## A) … ## G), чтобы машинный формат и языковой мандат перестали конкурировать (жёсткий мандат ронял D–G целиком, A–G ✗ 2/3 на проде). - Доля письма считается по прозе (CAR-75): таблицы, блок SCORE_SUMMARY, код и URL ничего не говорят о языке отчёта. Японский отчёт с латинскими таблицами давал 26–27 % родного письма при полном тексте и флаковал на гейте 30 %; по прозе держится детерминированно.
- /api/estimate показывает языковое предупреждение (CAR-76): оба провайдерских хвоста добавляют
report not in <locale>, когда сохранённый отчёт не на языке, а remote-qa печатает коды предупреждений колонкой таблицы (только коды — лог публичный).
Примечания
- Не портовано (задокументировано): инфраструктура scan-ats-full, регистратор истёкших в liveness (чистый планер — когда появится писатель scan-history), coverage-check/discover/story-bank/pdf –ats/text mode/domain_filter (только CLI), фикс req-id в dedup-tracker (у web-ui нет dedup-tracker), README.vi.md. Форк родителя защищает ADR-0001/0002: telegram.mjs выжил, Unicode LOCATIONISH_RE сохранён (апстрім откатил к ASCII \b; откат смёржился без конфликтов).
[1.248.7] — 2026-10-11
Роль из шапки отчёта теперь читает реальную конвенцию родителя — # Evaluation: {Company} — {Role} — вместо предположения # <Роль> — <Компания>, так что в трекер больше не попадает «Evaluation: <Компания>» как роль; отчёт не на том языке предупреждает вместо молчаливого сохранения; а болтовня модели до H1 срезана из сохранённых отчётов.
Исправлено
- Роль читается из шапки в формате родителя: сначала снимается локализованная текстовая метка (
Evaluation:,Оценка вакансии:,Оцінка:и ещё 16 вариантов локалей — от длинных к коротким, форма регэкспа та же, что у text-label регэкспа в parsers.mjs). После метки форматCompany — Role→ роль справа; без меткиRole — Company→ слева; часть, называющая компанию, решает независимо. Роль никогда не равна компании и не бывает голым словом метки — запасной вариант это чищеная строка поста. (v1.248.6 читала левую часть и выдавалаEvaluation: Sveakкак роль в каждой строке трекера от auto-pipeline.) - cleanup-plan считает строки, а не файлы: строки
— N dirty-URL line(s)суммируются (берётся N), а не считаются по одной — файл с 5 мусорными URL давал 1, а не 5. - Отчёт evaluate не на том языке предупреждает вместо молчаливого сохранения: docs-ассистент отвечает на языке, а evaluate дрейфовал (доля тамильского 22 % и 2 %, повторено 2/2 на проде; ja флакнул раз и прошёл на повторе). Промпт оценки теперь открывается И закрывается обязательной языковой инструкцией — весь отчёт, включая таблицы, — а конвейер проверяет язык сохранённого отчёта: дрейф всплывает предупреждением
report not in <locale>в событии done и в детале шага, при этом (иначе валидный) отчёт сохраняется. - Без болтовни модели до H1: преамбула «I’ll evaluate this job description… Reading files…» срезается при сохранении — отчёт начинается со своей заголовочной строки.
Примечания
- Не портировано и не тронуто (проверено в регрессе v1.248.6): embed-фетч, строчная структура поста («Компания : X»), дедуп «## Rejected», отказ корня домена до fetch, честность recon/link-check и cleanup-режимы. Языковая проверка — предупреждение, не отказ: отчёт (контент уже провалидирован A–G + оценка) сохраняется; оператор видит дрейф (
report not in <locale>в событии done и в детале шага) и может повторить оценку.
[1.248.6] — 2026-10-10
Пострегрессионная шлифовка телеграм-цепочки: реальный пост сохраняет строчную структуру (метка «Компания : X» и строка роли находятся там, где они реально есть), роль в трекер приходит из шапки отчёта, а не сырой строкой поста, и операционные поверхности (recon, cleanup-plan, link-check) сообщают то, что заявляют.
Исправлено
- Пост t.me больше не склеивается в одну строку:
extractTelegramPostTextпревращал каждый тег в пробел — включая<br>— поэтому строчно-якорные шаблоны (^Компания:) не срабатывали, а роль проваливалась до первых 100 символов всего поста.<br>(и закрывающий</blockquote>) теперь дают перевод строки до снятия тегов, числовые HTML-сущности (&#NN;,&#xNN;) декодируются. Шаблон метки компании расширен до^Компания\s*[:—–-]\s*(.+)— живой пост говорил «Компания : Sveak» с пробелом перед двоеточием, и в трекер попадало имя канала. - Роль в трекере берётся из шапки отчёта: конвейер вычислял объединённую роль (H1 шапки → строка поста как запасной вариант) для гейта, но в slug, строку трекера и событие done писал сырой
guess.role. H1 шапки следует конвенции# <Роль> — <Компания>— берётся сторона роли (компания не приезжает следом); строка-запасной вариант чистится — без хэштегов (#удаленка), без эмодзи, ≤80 символов. ## Rejectedбольше не дублируется: каждый отказ дописывал свежий заголовок## Rejectedвdata/pipeline.md. Раздел создаётся один раз; каждый отказ дописывает свою строку в существующий раздел (два отказа подряд → один заголовок, две строки).- Корень домена отклоняется до запроса: проверка
pathnameшла послеfetchJobDescription— мусорныйlinkedin.com/сначала скачивался целиком (fetch: done) и только потом отклонялся. Теперь проверка срабатывает до fetch — мусорный URL не стоит ничего. - Recon вычисляет сигнал skip-контракта, а не утверждает его:
honours rejected:теперьyes, только если в задеплоенномauto-pipeline.mjsреально естьmarkPipelineRejected(греп по выкачанному исходнику), а счётчик меток больше не печатает «0» дважды (grep -cсам печатает 0 при отсутствии совпадений; фолбэк|| echo 0дублировал его). - cleanup-plan сообщает числа по видам: отчёты t-role к перемещению, строки dirty-URL в pipeline, строки test-row в трекере — вместо одного
grep -c, который считал собственную строку успеха «no unscored -t-role-.md reports» (чистые данные печатали фантомную «1»). - link-check считает 5xx от github.com непроверенными: три прогона подряд отдавали 503 на
github.com/…/blob/main/…с раннеров Actions, тогда как те же URL отвечали 200 из других сетей — считается за бот-стеной (как 401/403/429), а не битой ссылкой.
Примечания
- Не портировано и не тронуто (проверено в регрессе v1.248.5): сам embed-фетч, серверный skip-контракт, офлайн-тесты _setLookup, приём uk-порядковых заголовков, границы слов в гейте роли и режим cleanup-apply. Подсчёт по видам в cleanup-plan читает собственные метки вывода скрипта очистки (
no valid SCORE/dirty-URL line/test-row line) — их переименование молча сломало бы счётчики.
[1.248.5] — 2026-10-10
Пострегрессионное укрепление: записи-одиночки t.me теперь фетчат сам текст поста (embed-форма), отклонённая запись покидает пул eval-таймера на стороне сервера, родная нумерация блоков в украинском отчёте принимается валидатором — а один тест, тайком ходивший в живую сеть, навсегда офлайн.
Исправлено
- Записи t.me наконец оцениваются: обычная страница одного поста отдаёт в HTML только шапку канала (~0,2 КБ — «View in Telegram» и ничего больше), модель получала пустое описание, и каждая телеграм-запись умирала на evaluate. Фетч теперь переписывает
t.me/<канал>/<id>(и форму лентыt.me/s/…) в embed-форму (?embed=1&mode=tme) — тот же хост, SSRF-проверки без изменений — и берёт текст поста из.tgme_widget_message_text(запасной вариант:og:description). Нет текста виджета → отказ на шаге fetch (telegram post has no text,rejected: true) до траты токенов LLM. - Eval-таймер перестаёт повторно платить за отклонённые записи (серверный контракт): отклонённая запись помечается в
data/pipeline.md— её строка покидает URL-фенс и попадает в раздел## Rejectedс короткой причиной (идемпотентно, без дублей). Таймер берёт голые URL из фенса, поэтому помеченная запись пропускается естественно — без зависимости от внутренностей скрипта таймера. Recon печатаетpipeline rejected marks: N+honours rejected: yes (server-side)вместо грепа по скрипту. - Украинские отчёты снова проходят валидацию A–G: uk-модель нумерует блоки по порядку собственного алфавита (А Б В Г Д Є Ж), а проход двойников засчитывал только А/В/Е — на проде отсутствовали ровно блоки C, D, F, G (2/2 воспроизведения). Валидатор принимает украинскую порядковую последовательность, когда заголовки идут по порядку (та же дисциплина, что абджад для ar и деванагари для hi), а промпт оценки для всех локалей теперь явно требует латинские
A–G. - Гейт ролевых ключевых слов срабатывает на слова, а не подстроки: корни
linkedin.com/иx.com/проходили гейт, потому чтоlead/itжили в служебном тексте страницы. Ключевые слова сопоставляются по границам слов Unicode, а корень домена (URL без пути) отклоняется до вызова LLM. - Юнит-тесту больше не нужна живая сеть: тест no-hints (
tests/auto-pipeline-jd-gate.test.mjs) подменял HTTP-транспорт, но делал настоящий DNS-запрос — в CI был зелёным только потому, что у раннера была сеть. В safe-fetch появился хук_setLookup()(сам резолвер заменяем в тестах; продовый код его не трогает), тест резолвит офлайн.
Добавлено
- Режимы
cleanup-plan/cleanup-applyв deploy.yml: продовая очистка QA-мусора (post-qa-cleanup) запускается на сервере без SSH с ноутбука. В логе только числа — ни имён файлов, ни превью строк: лог публичный, а в именах названия компаний. Шаг Verify для cleanup-режимов пропускается.
Примечания
- Не портировано: компилятор локаций по границам слов и таблица USPS из родителя (задокументировано с v1.248.1). Контракт пропуска отклонённых записей держится на сервере (разметка в data/pipeline.md), а не на скрипте таймера — тот вне репозитория и намеренно не правится.
[1.248.4] — 2026-10-10
Цепочка роли для Telegram-записей: кириллический пост с настоящей вакансией теперь даёт именованный отчёт вместо отклонённого unknown-role файла — а вызов LLM происходит только для записей, которые стоит оценивать.
Исправлено
- Цепочка роли работает для кириллических постов: список ключевых слов получил разработчик/программист/инженер/аналитик/менеджер/руководитель/тимлид/лид/дизайнер/тестировщик/QA/архитектор/специалист/директор/DevOps/SRE (старый список был EN-only — 0 из 40 реальных постов получили роль), консультируется шапка отчёта (
parseReportHeader— модель пишет роль в H1), последняя инстанция — первая осмысленная строка поста. - Гейт идентичности переехал ДО вызова LLM: запись без компании и без зацепки роли падает на шаге fetch (
rejected: true) — токены не тратятся на оценку, которая всё равно была бы отклонена. t.me/s/<канал>/<id>больше не называет компанию «S» — сегмент веб-превью пропускается; обе формы ссылок резолвятся в канал.- Самоябели «Компания: X» на Telegram-постах учитываются (и «…в компании X» внутри предложения для мессенджер-хостов).
Добавлено
- Фейлы auto-pipeline несут
rejected: trueв SSE-ошибке — серверный eval-таймер использует флаг, чтобы пропустить запись pipeline вместо повторов каждые 2 часа.
Примечания
- Не портировано: родительский компилятор локаций с word-boundary и USPS-таблица (документировано с v1.248.1).
- Блок «Translations» в README генерируется из одного канонического списка локалей, в test:ci добавлен паритет-тест (17 ссылок на файл, своя не линкуется) — ar/pl/uk получили блок; дрейф разделителей
|/·устранён.
[1.248.3] — 2026-10-10
Продолжение регресс-раунда: eval-таймер больше ни при каких условиях не оформит безымянные Telegram-заглушки в отчёты, QA-очистка больше не удалит настоящие Telegram-вакансии, атрибуция карты снова видна, дашборд-чипы читаются с ≥ 4.5:1.
Исправлено
- Auto-pipeline валидирует ДО сохранения: отчёт без оценки 0–5 или без блоков A–G завершает ран на шаге evaluate (
rejected: trueв SSE-нагрузке) — ни файла отчёта, ни строки трекера. t.me-записи, прошедшие длину-гейт v1.248.2, после того релиза дали ещё два мусорных отчёта; теперь безымянные записи отклоняются сcompany/role not identifiable. guessCompanyRoleбольше не выдумывает компанию из мессенджер-хостов —t.me,telegram.me,vk.com,linkedin.comи другие в EMPTY_DOMAINS: у Telegram-поста fallback на имя канала, а безымянная запись отклоняется вместоunknown-role.post-qa-cleanupсначала читает оценку из шапки: auto-pipeline вырезает блок SCORE_SUMMARY перед сохранением, поэтому старая проверка только по блоку считала каждый сохранённый Telegram-пост мусором — на проде 8 из 41 несут оценку в шапке, и--applyудалил бы их. Оценка из шапки (черезparseReportHeader) теперь первичный сигнал, блок — запасной. Dry-run печатает причину по каждому файлу.- Атрибуция Leaflet видна на #/map — docs-FAB её перекрывал (нарушение лицензии OSM); угол поднят на 96px.
- Дашборд-чипы ≥ 4.5:1 — подписи
dash-chipи вариант--manualпереведены на--foggy-strong(было 4.31:1).
Добавлено
auto-pipeline-фейлы несутrejected: trueв SSE-ошибке — серверный eval-таймер использует флаг, чтобы пропустить запись pipeline вместо повторных попыток каждые 2 часа.
Примечания
- Дрейф ar/ja не повторился на перезапуске (см. v1.248.2).
- Сама очистка прода по-прежнему ждёт явного «да» пользователя (CAR-61c); с этим фиксом очистка больше не может удалить настоящие Telegram-вакансии.
[1.248.2] — 2026-10-10
Регресс-раунд: eval-таймер больше не превращает мусор pipeline в «Last evaluation», разбор SCORE выживает в реальных форматах, а трекер наконец говорит на вашем языке.
Исправлено
- Пустые JD-отчёты не создаются. Гейт JD в auto-pipeline поднят с 50 до 200 символов до вызова LLM — страницы-заглушки (
example.com/qa-v167-…) заканчиваются SSE-ошибкой + предупреждением в консоль вместо отчёта…-t-role-<ts>.md, который дашборд показывал как «Last evaluation» (на проде 42 таких файла). - Разбор SCORE_SUMMARY выживает в реальных форматах: «4,2/5», «4.2 / 5», «4.2», «SCORE :» — всё валидно; запятая с 1–2 цифрами — десятичная, с 3 — тысячи (старый код читал «4,200» как 4.2 и пропускал гейт 0–5). Провал проверки пишет в лог только строку
SCORE:— никогда содержимое отчёта (логи CI публичные). - Статусы трекера переведены: табы стадий и бейджи рендерят локализованные подписи во всех 18 локалях; каноническое значение в данных, фильтрах и URL не тронуто.
- Честный ETA: «⏱ ~30s» стал «~2–4 мин» в evaluate/advisor/market-report (реальный диапазон 86–285 с), а подзаголовок evaluate читается A–G.
- Доступность: счётчик API-ключей в config и счётчик pipeline теперь ≥ 4.5:1 (было 4.31:1); чип фасета с пустым значением больше не рендерится без доступного имени.
- UI-полировка: кнопка Delete у сохранённых поисков выглядит деструктивной только при выбранном поиске (иначе disabled); карточка EMAIL профиля не обрезается на 1440 px; у пилюли live-evals появился отступ иконка/текст включая RTL; docs-FAB не перекрывает атрибуцию Leaflet; «?» помощи не переносится под табами stats; у usage появился воздух между подзаголовком и табами; health теперь говорит career-ops-ui.
Добавлено
scripts/post-qa-cleanup.mjs: dry-run по умолчанию,--applyс автоматическими бэкапами (*.bak-<ts>, отчёты переносятся вqa/cleanup-backup-<ts>/) — строки ZZ-QA-TEST в трекере, отчёты*-t-role-*без валидного SCORE, записиexample.comв pipeline.
Примечания
- Дрейф ar/ja из регресс-раунда не повторился на перезапуске (оба: lang ✓ + A–G ✓) — запись ta в language-check и усиленный парсер SCORE идут как defense-in-depth.
- Сама очистка прода ждёт явного «да» пользователя: сначала dry-run-план, потом
--apply.
[1.248.1] — 2026-10-09
location_filter получает родительские тиры always_allow и block_hard (CAR-60) — мультистрановая строка локации выживает при блок-листе соседей, а явный жёсткий блок больше нельзя спасти.
Исправлено
always_allowсильнееblock: приblock: [romania, poland, croatia]иalways_allow: [united kingdom]workable-merged ячейка «London, United Kingdom · Romania · Poland · Croatia» проходит — домашний регион есть в опциях (потребительский кейс workable #4806 теперь в родительской форме).block_hardсильнееalways_allow: страновые термины, которые нельзя спасти совпадением always_allow где-то в строке.strict: trueучитываетblock_hardкак ограничивающий тир; секцияlocation_filterв docs/help документирует оба тира ×18.
Примечания
- Осознанно не перенесено сейчас: родительский компилятор локационных ключей с word-boundary и таблица штатов USPS (веб-морда держит case-insensitive substring; таблица служит US-центричным конфигам — файл под CAR-60, если понадобится US-конфиг). Промежуточная allow-list форма consumer-теста workable остаётся вторым вердиктом рядом с новой родительской.
[1.248.0] — 2026-10-09
Паритет с родителем: пять апстрим-фиксов провайдеров и liveness перенесены — борды, переехавшие на новые API, продолжают работать, а баннер закрытой вакансии на отдельной строке больше не пропускается.
Исправлено
- Gupy следует за API на
portal.gupy.ioи шлёт имена фильтров, которые API реально читает (workplaceType/type) — старые плюральные имена молча игнорировались;countryбольше не отправляется (сконфигурированная страна рисует warning с именем записи вместо отправки). Конфиденциальные работодатели дропаются по точному совпадению ярлыка («Confidencial Seguros» выживает), аmax_pagesограничен 100 — API отвечает 400 после offset 10 000, и truncation-warning теперь советует сузить ключевые слова, а не поднимать потолок. - Workable сохраняет все страны мультистрановой вакансии: сиблинги под одним job-URL схлопываются в одну строку («London, United Kingdom · Romania · Poland · Croatia») вместо победы первой страны.
- Liveness: баннер на своей строке — не часть строки выше — «Sign in if you want to apply» / «Get notified when this position is filled» под вакансией читается как клауза закрытия, а настоящее «until filled» через строки по-прежнему читается активным.
- Radancy читает сиблинг-элемент
job-locationв legacy-разметке карточки (раскладки семьи Barclays кладут локацию после</a>) — строки без собственной локации остаются пустыми, а не занимают локацию соседа. - SmartRecruiters fallback-ссылки сохраняют сконфигурированный slug — вакансия с отсутствующим/недоверенным
refсинтезирует публичный URL из slug тенанта, никогда из отображаемого имени.
Добавлено
- No-ports задокументированы: апстрим-фикс
_httpproxyfetchImpl(в веб-морде нет прокси-ветки — порт сломал бы identity-проверку DNS-rebinding-гварда и добавил undici-зависимость, от которой репо сознательно уходит); апстримныеverify-ats(+226 строк) иcv-facts(веб-морда реализует свой ATS-скоринг и CV-парсинг); родительский дашборд Go TUI и CLI-скрипты (веб-морда не шеллится в них); перестановка импортов в Teamtailor (косметика).
Примечания
- Известный пробел перенесён как follow-up: родительский tier
always_allowв location-фильтре (потребительский тест workable выживает UK-блок благодаря ему) — в location-фильтре веб-морды этого tier нет; порт выразил тот же вердикт потребителя через allow-list тест. Заведено отдельной задачей.
career-ops-ui v1.267.1 · page 2/13 · mirror of the project changelog for cvstart.org