Журнал изменений

GitHub

Каждое значимое изменение, релиз за релизом. Тот же файл публикуется в репозитории и переведён на все 19 языков.

[1.100.0] — 2026-07-05

Two-pager: ИИ-автозаполнение из вашего резюме + Просмотр + экспорт в PDF/DOCX/Markdown. Two-pager (#/two-pager) фиксирует то, чего вы на самом деле хотите от следующей роли, но раньше каждое поле приходилось писать вручную или копировать промпт в другой инструмент. Теперь ✨ ИИ-помощник заполнения работает вживую с настроенным провайдером — читает только ваше резюме + профиль (через bundleProjectContext, ничего не выдумывая), составляет все поля (кто я / что нравится / обязательное / что ненавижу / стоп-факторы / непреложное / целевая среда) и заполняет форму, чтобы вы проверили, отредактировали и сохранили. Без API-ключа возвращается к модалке «скопировать промпт», как раньше. Новая кнопка 👁 Просмотр и экспорт отображает two-pager как форматированный документ с панелью Скачать .md / Сохранить как PDF / Сохранить как DOCX / Копировать.

  • Экспорт .docx без зависимостей. Новый server/lib/docx.mjs формирует минимальный, но валидный Office Open XML .docx (ZIP с DEFLATE из четырёх частей OOXML, CRC-32 на запись) — без новой зависимости (зависимости остаются express + js-yaml). Новый маршрут POST /api/export/docx (server/lib/routes/export.mjs, 26-й модуль маршрутов; без состояния, лимит 200 КБ, без записей / без LLM / без fetch по URL). Подключён к общему public/js/lib/report-export.js, поэтому отчёт по рынку, карьерный план и профориентация тоже получают экспорт в DOCX.
  • Живое автозаполнение использует общий каскад провайдеров (runActiveProvider / providerAvailable); возвращённый YAML парсится и приводится к ограниченной форме two-pager (parseYamlFields + normalizeTwoPager) — неизвестные ключи отбрасываются, массивы/строки ограничиваются. Ручной режим сохранён.
  • Тесты: tests/export-routes.test.mjs. 4 новых ключа i18n ×16 (export.saveDocx, twoPager.preview, twoPager.aiFilling, twoPager.aiFilled).

Новое: server/lib/docx.mjs; server/lib/routes/export.mjs.

[1.99.0] — 2026-07-05

Страница здоровья порталов (#/portals). Сканер следит за набором компаний в portals.yml; slug ATS может тихо сломаться, и этот работодатель исчезает из всех будущих сканирований. Новая страница Portals перечисляет каждую отслеживаемую компанию и по кнопке Check portal health проверяет каждый careers_url через safeGet с закреплённым DNS (защита от SSRF), помечая мёртвые (404 = тихо отброшена) — только чтение. Также усиливает репортёр багов v1.98.0 по итогам ревью: кольцевой буфер ошибок теперь ловит сетевые сбои fetch, а скраббер вычищает непомеченные ключи провайдеров.

Новое: server/lib/routes/portals.mjs; public/js/views/portals.js.

[1.98.0] — 2026-07-05

Встроенный репортёр багов (паритет с веб-частью web-v0.2.0 родительского проекта). Кнопка 🐞 Report a bug в панели уведомлений собирает диагностический снимок с приоритетом приватности — версии, ваш экран, браузер, сводку проверок /api/health и последние 20 ошибок из нового клиентского кольцевого буфера — плюс детерминированный отпечаток для дедупликации (co-web-<base36>), даёт просмотреть точный Markdown и затем открывает предзаполненный issue на GitHub. Ничего не отправляется автоматически; он никогда не несёт ваше CV, профиль, ответы, URL вакансий или ключи. Новые библиотеки logbuf.js + bug-report.js; 11 ключей i18n ×16; tests/bug-report.test.mjs.

Новое: public/js/lib/logbuf.js; public/js/lib/bug-report.js.

[1.97.1] — 2026-07-05

Усиление по итогам ревью и паритет документации (продолжение v1.97.0). Разбор логов AI-ревью выявил реальные исправления:

  • fit-score.js (значок соответствия ◎ при сканировании). salaryFloor() больше не превращает ставку меньше годовой в фиктивный годовой минимум — «at least 500 EUR/day», «$80/hr», «6000 monthly» теперь возвращают null вместо дилбрейкера в 500k/80k. Сопоставление стран теперь идёт по целому слову (\b…\b), поэтому «Germany» больше не совпадает с прилагательным «German» (равно как «Nigeria» внутри «Nigerian») и не срабатывает ложным нарушением «обязательно-в-другом-месте». +3 теста в tests/fit-score.test.mjs.
  • Паритет документации. Каждый локализованный README теперь единообразно заявляет 16 локалей — счётчик/список в строке Help (×13) и текст раздела локализации плюс примечание «добавьте ключ во все N файлов» (×8) всё ещё держались на счётчиках до v1.85 (8/9). Счётчик адаптеров §17 во встроенной справке исправлен на 46 адаптеров — 41 английский + 5 русских во всех 16 бандлах.

Никаких изменений поведения помимо эвристики значка соответствия; ни новых маршрутов, ни ключей, ни добавлений i18n.

[1.97.0] — 2026-07-05

Источник сканера Dassault Systèmes + фронт улучшений качества по трём направлениям.

  • Новый источник сканирования — Dassault Systèmes (паритет с родительским career-ops, #1498). server/lib/sources/dassault.mjs + server/lib/portals/adapters/dassault.mjs воспроизводят zero-token-провайдер «карточного поиска» Exalead из родительского проекта (публичная лента за 3ds.com/careers/jobs). Это единственная глобальная конечная точка, поэтому она выбирается по провайдеру (provider: dassault) или автоматически определяется по хосту 3ds.com, с защитой от SSRF, привязкой хоста к www.3ds.com и redirect:'error'. XML разбирается без DOM (карты <Meta> для каждого <Hit>), город/страна извлекаются из локализованной строки категории, а вакансии сохраняются только тогда, когда их публичный URL находится на *.3ds.com. Реестр теперь поставляет 46 адаптеров (41 EN + 5 RU); счётчик ALL_ADAPTERS, проверки сортированных id и EN-набора /api/scan/sources подняты с 40 → 41. Набор tests/sources-dassault.test.mjs (10 случаев).
  • Перенесённые из родительского проекта исправления надёжности. Парсер Avature теперь допускает два живых варианта разметки арендаторов (article--result с суффиксом позиционного индекса + безклассовый якорь заголовка JobDetail, #1541); Get on Board защищается от 0/отрицательного published_at (больше никаких ложных дат 1970 года); SuccessFactors ограничивает последнюю страницу, чтобы она не могла превысить MAX_JOBS (#1528).
  • Исправления серверного аудита. safe-fetch больше не зависает при ответе, превышающем лимит размера — путь ограничения размера теперь напрямую завершает промис вместо ожидания события 'end', которое разрушенный поток никогда не испустит (исправляет запросы /api/pipeline/preview и авто-пайплайна для больших страниц). Логирование активности SSE stream.* снова достижимо (проверка /api/stream/ перемещена выше общей защиты «пропускать GET»).
  • Исправления аудита SPA. Переключатель вкладок #/stats защищён от гонки при асинхронном рендеринге — результат медленной вкладки больше не может затереть более новую вкладку, на которую пользователь уже переключился. Подтверждения удаления в mock interview и нетворкинге теперь передают корректные заголовок + тело (больше никаких диалогов с пустым телом).
  • Исправления перевода. Исправлены непереведённые значения словаря — украинские config.modes* (Adaptive Framing / Exit Narrative / Location Policy), русский eval.jdLbl («Job Description»), итальянский dash.quick.contactoSub («referral» → «segnalazione») — а также английская заготовка 16 локалей локализована в CHANGELOG для ru/uk/ja/ko/zh-CN/zh-TW.

Новое: server/lib/sources/dassault.mjs; server/lib/portals/adapters/dassault.mjs.

[1.96.0] — 2026-07-04

Карьерная ориентация (Epic 27). Новая страница #/orientation отвечает на вопрос «какие направления мне действительно подходят?» — такой ответ вы получили бы от профориентационного теста, но выведенный из вашего собственного резюме и профиля, а не из анкеты. Нажмите Сгенерировать профиль, и модель вернёт ваши наиболее подходящие карьерные векторы (какие из восьми архетипов — Функционалист, Администратор, Коммуникатор, Специалист, Аналитик, Новатор, Управленец, Предприниматель — подходят, с подтверждениями), склонность к карьерному типу, рекомендуемые роли, профессиональные сильные стороны, привязанные к вашему резюме, особенности стиля работы и рекомендации по развитию. Это ИИ-размышление о том, как читается ваше резюме, — а не психометрический тест: оно никогда не выдумывает достижения и не приводит числовые оценки так, будто они были измерены. Экспортируйте профиль в Markdown или PDF; на диск ничего не записывается.

  • Новый маршрут server/lib/routes/orientation.mjs (24-й модуль маршрутов) — POST /api/orientation/generate строит промпт профиля из CV+профиля+two-pager+памяти через общий каскад провайдеров, с ручным резервным вариантом для копирования и вставки и без записи файлов.
  • Переиспользует report-export.js для Markdown/PDF/копирования, в навигационной группе Развитие.
  • Тесты: tests/orientation-routes.test.mjs (формулировка «размышление» / отсутствие сфабрикованных оценок, ручной режим на основе CV/профиля). 7 новых i18n-ключей ×16 локалей, справка §28 ×16.

Новое: #/orientation; server/lib/routes/orientation.mjs.

[1.95.0] — 2026-07-04

План карьеры (Epic 26). Новая страница #/career-plan превращает ваше резюме и профиль в конкретный, персонализированный план развития. Выберите горизонт (6/12/24 месяца) и, при желании, фокус, и модель — читая ваше CV, профиль, two-pager и заметку памяти — пишет снимок отправной точки, SWOT сильных сторон и роста, цели в формате SMART / OKR / WOOP, альтернативные траектории, план hard/soft-навыков, помесячную дорожную карту, методы отслеживания прогресса, подводные камни и поддерживающие шаги. Она планирует вперёд от того, что действительно показывают ваши материалы, и никогда не выдумывает факты о вашей истории. Редактируйте её на месте, Сохраните её в пользовательский слой (config/career-plan.md) и экспортируйте её в Markdown или PDF.

  • Новый маршрут server/lib/routes/career-plan.mjs (23-й модуль маршрутов) — GET/PUT /api/career-plan (пишет config/career-plan.md) + POST /api/career-plan/generate (общий каскад провайдеров, ручной откат, без фабрикации). PATHS.careerPlan.
  • Переиспользует общий report-export.js (v1.94.0) для Markdown/PDF/копирования и новую навигационную группу Рост.
  • Тесты: tests/career-plan-routes.test.mjs (ограничение, круговой обход GET/PUT, промпт с учётом горизонта, засеянный из CV/профиля). 20 новых ключей i18n во всех 16 локалей, Справка §27 во всех 16.

Новое: #/career-plan; server/lib/routes/career-plan.mjs; PATHS.careerPlan.

[1.94.0] — 2026-07-04

Статистика, переработана (Epic 25). Страница #/stats теперь представляет собой раздел Статистика с тремя вкладками, с настоящими графиками и куда большим объёмом данных. Новая вкладка Отчёт по рынку запрашивает у модели анализ зарплат и рынка труда по вашим целевым ролям в выбранных вами регионе и валюте — резюме для руководства, зарплата по грейдам с процентилями P10/P25/P75/P90, ведущие работодатели, таблица востребованных навыков, частота бенефитов, распределение офис/гибрид/удалёнка, тренды на 12–24 месяца и рекомендации по переговорам. Каждая цифра помечается как ориентировочная оценка на основе знаний модели и никогда не подаётся как собранные данные. Новая вкладка Мой pipeline строит графики по вашему собственному трекеру: распределение баллов, воронка статусов, ведущие компании и роли, отклики во времени и коэффициенты конверсии. Исходное представление целевых ролей (вакансии/зарплата по странам + тренд сохранённых снимков) переезжает на третью вкладку, теперь с выбором валюты и обзором вакансий по ролям.

  • Экспортируйте любой отчёт в Markdown или PDF либо скопируйте его — через общий помощник report-export.js (загрузка Markdown-blob; PDF через существующий встроенный PDF-раннер).
  • Новый маршрут server/lib/routes/market.mjs (22-й модуль маршрутов) — POST /api/stats/market строит промпт анализа рынка на основе вашего CV/профиля (чтобы он знал ваши целевые роли), региона и валюты, прогоняет его через общий каскад провайдеров и откатывается к промпту для копирования и вставки при отсутствии ключа. Без записи на диск.
  • Тесты: tests/market-routes.test.mjs (ограничение региона/валюты, промпт с пометкой о честности, ручной режим с посевом из CV/профиля). 36 новых ключей i18n во всех 16 локалей, Справка §26 во всех 16.

Новое: #/stats переработана во вкладки; server/lib/routes/market.mjs; public/js/lib/report-export.js.

[1.93.0] — 2026-07-04

Слой памяти (Epic 24). Новая страница #/memory хранит короткую редактируемую заметку «запомни это обо мне», которую ассистент держит в уме при каждой задаче:

  • Одна заметка везде — поскольку она встраивается в bundleProjectContext, заметка автоматически доходит до каждого AI-запроса (оценка, mock interview, нетворкинг, CV Studio) по всем провайдерам. Напишите её один раз — она направляет всё.
  • Направление, а не факты — она фиксирует ваши предпочтения и то, как вам нравится работать (тон, формат, deal-breakers, ритм), и никогда не вводит новые фактические утверждения о вашем опыте — они по-прежнему живут только в вашем CV, профиле и two-pager. Сохраняется в пользовательский слой в config/memory.md и никогда не перезаписывается обновлениями.
  • Предложить из ваших данных — POST /api/memory/suggest анализирует ваш собственный трекер откликов на предмет поведенческих паттернов и набрасывает пункты, которые вы можете просмотреть и отредактировать. Он читает ваш трекер; он никогда не выдумывает факты и не делает живых вызовов.

Новое: server/lib/routes/memory.mjs (21-й модуль маршрутов — GET/PUT /api/memory + POST /api/memory/suggest), public/js/views/memory.js, PATHS.memory и блок config/memory.md, добавленный в bundleProjectContext. 11 новых ключей i18n во всех 16 локалей. Тесты: tests/memory-routes.test.mjs.

[1.92.0] — 2026-07-04

CV Studio (Epic 21). Новая страница #/cv-studio даёт вашему резюме три честных, в основном локальных инструмента:

  • Диагностика резюме — детерминированная оценка 0–100 с пояснениями по каждой проверке (количественно измеримое влияние, слабые глаголы, штампы, длина, ключевые разделы, контактные данные). Полностью на стороне клиента (window.CvDiagnostics) — без LLM, ничего выдуманного, каждая находка объяснена, так что вы решаете, что менять.
  • Маска приватности — скрывает PII (email, телефон, ссылки/ники, домашний адрес и, по желанию, ваше имя → инициалы) перед тем, как поделиться резюме в виде образца или скриншота. Работает целиком в браузере (window.CvPrivacy); сообщает ровно то, что было скрыто, и никогда не хранит оригинал.
  • Make it human / подгонка под голос — вставьте сухую строку или абзац, и он перепишет её вашим голосом, обоснованно на стороне сервера через voice-dna.md и writing-samples/. Жёсткое ограничение: он может переставлять, ужимать и перезвучивать, но никогда не вводит факт, метрику или достижение, которых ещё нет в тексте. Выполняется вживую через общий каскад провайдеров либо возвращает готовый к копированию промпт без ключа.

Новое: server/lib/routes/cv-studio.mjs (20-й модуль маршрутов — POST /api/cv-studio/humanize), public/js/views/cv-studio.js, public/js/lib/cv-diagnostics.js, public/js/lib/cv-privacy.js, PATHS.voiceDna + PATHS.writingSamplesDir. 29 новых ключей i18n во всех 16 локалей. Тесты: tests/cv-diagnostics.test.mjs, tests/cv-studio-routes.test.mjs. (Галерея шаблонов, экспорт в Word и архив PDF вакансий отслеживаются как последующая работа над CV Studio.)

[1.91.0] — 2026-07-04

Нетворкинг и глубокое исследование компаний (Epic 16). Новая страница #/networking превращает компанию в действенный план по получению собеседования, обоснованный вашим резюме, профилем и two-pager:

  • Досье компании — сжатая справка о том, чем занимается компания, недавние сигналы, достойные упоминания, и зацепки «почему я подхожу», взятые из вашего реального опыта.
  • С кем связаться — 3–5 целевых персон (нанимающий менеджер, внутренний рекрутер, старший IC из команды, тёплый контакт/выпускник того же вуза) с конкретной поисковой строкой LinkedIn для нахождения каждого. Реальные имена никогда не выдумываются.
  • Самый тёплый путь для представления — единственный наиболее реалистичный тёплый вход именно для вашего опыта (общий работодатель/учебное заведение/сообщество, путь второго уровня или сильный по сигналу холодный DM) и почему.
  • Черновики для контакта — короткие, конкретные сообщения для главных персон, обоснованные вашими реальными точками доказательства.
  • Вживую или вручную — выполняется вживую через общий каскад провайдеров с любым ключом либо возвращает готовый к копированию промпт (честный fallback, ничего выдуманного). Save plan сохраняет готовый план в пользовательский слой (networking/net-{company}-{role}-{date}.md); страница показывает список, открывает и удаляет сохранённые планы.

Новое: server/lib/routes/networking.mjs (19-й модуль маршрутов), public/js/views/networking.js, PATHS.networkingDir. Переиспользует каскад server/lib/llm-dispatch.mjs из v1.90.0. 24 новых ключа i18n во всех 16 локалей. Тесты: tests/networking-routes.test.mjs.

[1.90.0] — 2026-07-04

Mock Interview 2.0 (Epic 15). Новая страница #/mock-interview превращает ваше резюме, профиль, two-pager и банк историй в пошаговую репетицию собеседования:

  • Диалоговая практика — задайте целевую роль (+ по желанию компанию / JD), и интервьюер начинает с точечного вопроса. На каждый отправленный ответ вы получаете структурированную реплику: Feedback (сильные стороны + пробел по STAR+R), Score (N/5) и Next question, который прощупывает самое слабое место вашего последнего ответа. Обосновано на стороне сервера вашими реальными материалами — оно никогда не выдумывает опыт, которого у вас нет.
  • Учитывает банк историй — interview-prep/story-bank.md встраивается в промпт (тот же уровень доверия, что и cv.md), поэтому обратная связь может указать вам на ваши же лучшие истории.
  • Вживую или вручную — при наличии ключа провайдера ход выполняется вживую через общий каскад провайдеров (Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models); без ключа вы получаете готовый к запуску промпт для копирования и вставки (честный fallback, без выдуманных ответов).
  • Сохранённые сессии — нажмите Save transcript, чтобы сохранить завершённое собеседование в пользовательский слой (interview-prep/mock-{company}-{role}-{date}.md); страница показывает список, открывает и удаляет сохранённые сессии.

Новое: server/lib/routes/interview.mjs (18-й модуль маршрутов), public/js/views/mock-interview.js, server/lib/llm-dispatch.mjs (общий каскад провайдеров), PATHS.storyBank, bundleProjectContext({ extraFiles }). 30 новых ключей i18n во всех 16 локалей. Тесты: tests/interview-routes.test.mjs.

[1.89.0] — 2026-07-04

Соответствие кандидата и рынка — two-pager (Epic 14). Новая страница #/two-pager позволяет зафиксировать то, что вы на самом деле хотите от следующей роли, по образцу «Mnookin two-pager» из книги Never Search Alone:

  • Пошаговый конструктор — повествование от первого лица «Кто я», заметка «Целевая среда» и пять редакторов списков-чипов: loves, must-haves, hates, deal-breakers и non-negotiables. Сохраняется в пользовательский слой родительского проекта (config/two-pager.yml) через PUT /api/two-pager — никогда не перезаписывается системными обновлениями.
  • ИИ-помощник заполнения (POST /api/two-pager/draft) — собирает готовый к запуску Mnookin-промпт с вашим CV + профилем внутри, чтобы вы запустили его в любом LLM и вставили результат обратно. Он использует только ваши собственные материалы; ничего не выдумывается.
  • Бейдж соответствия-тому-что-вы-хотите — каждая вакансия на #/scan теперь показывает оценку соответствия ◎ N (на стороне клиента, через window.FitScore), сопоставляющую формат работы, страну, минимальную зарплату и релокацию вакансии с вашим two-pager. Честный по замыслу: если вакансия не даёт сопоставимого сигнала, бейдж не показывается (никогда не выдуманное число). Нарушения deal-breakers весят больше, чем лёгкие неприязни.
  • Питает каждую оценку — сохранённый two-pager встраивается в bundleProjectContext, поэтому все последующие оценки LLM сочетают заявленные вами предпочтения с соответствием CV-против-JD.

Новое: server/lib/routes/two-pager.mjs, public/js/views/two-pager.js, public/js/lib/fit-score.js, PATHS.twoPager. 27 новых ключей i18n во всех 16 локалях. Тесты: tests/two-pager-routes.test.mjs, tests/fit-score.test.mjs.

[1.88.0] — 2026-07-04

Доводка issue #29 — пробелы i18n в Сканировании + гигиена API.

  • Локализованы последние захардкоженные строки Сканирования (дорожная карта v1.69.4): плашки сводки по источнику (N новых / M совпадений), тосты N новых вакансий и бейдж reloc теперь проходят через t() — 4 новых ключа (scan.pillNew, scan.pillMatching, scan.newOffers, scan.relocBadge) во всех 16 локалях. Неанглоязычные пользователи больше не видят случайного английского в основном потоке сканирования.
  • Отключён заголовок X-Powered-By (дорожная карта v1.69.5): app.disable('x-powered-by') в createApp() — сервер больше не сообщает о том, что работает на Express. (Остальная часть этого эпика уже была выпущена: parentVersion вырезает свой комментарий release-please, переключатель светлой темы, закрытие модальных окон при смене маршрута и локализация «Score» (rep.score) в Отчётах.)

Тесты: tests/scan-i18n-gaps.test.mjs + проверка отсутствия X-Powered-By в tests/security-headers.test.mjs.

[1.87.0] — 2026-07-04

4 новых провайдера сканирования без авторизации (паритет с родительским career-ops v1.16.0). Реестр сканера вырастает с 41 → 45 адаптеров (40 EN + 5 RU) — все публичные, без авторизации, с закреплённым хостом, redirect:'error' (защита от SSRF), каждый с изолированным для CI тестом:

  • Get on Board (getonbrd) — публичный JSON:API по всему порталу (LATAM/удалённые технические роли), выбирается по провайдеру, с пагинацией. server/lib/sources/getonbrd.mjs.
  • Amazon (amazon) — публичный JSON поиска amazon.jobs, определяется по хосту или provider: amazon, пагинация по смещению. server/lib/sources/amazon.mjs.
  • Avature (avature) — ATS *.avature.net для каждого арендатора, парсинг HTML, определяется по хосту или provider: avature. server/lib/sources/avature.mjs.
  • SAP SuccessFactors (successfactors) — список плиток RMK для каждого арендатора (*.successfactors.eu/.com, jobs2web.com), парсинг HTML. server/lib/sources/successfactors.mjs.

Каждый поставляет sources/<slug>.mjs (автообнаруживаемый meta → выпадающий список #/scan) и portals/adapters/<slug>.mjs в ALL_ADAPTERS (правило двух реестров) + tests/sources-<slug>.test.mjs. Счётчик ALL_ADAPTERS, а также проверки сортированных id и EN-набора /api/scan/sources выросли с 36→40; GET /api/scan/sources теперь перечисляет 45.

[1.86.0] — 2026-07-03

Статистика по целевым ролям (#/stats) — рыночная статистика вакансий и зарплат по ВАШИМ целевым ролям. Новая страница аналитики читает ваши целевые роли из профиля (config/profile.yml → не захардкожены) и вакансии из последнего сканирования, а затем показывает по каждой роли и стране:

  • Вакансии по странам и медианную зарплату по странам (USD) — агрегируются на стороне клиента (public/js/lib/role-stats.js, повторно используя window.Countries) из разрозненных данных, которые сканеры уже собирают. Зарплаты в любой валюте нормализуются в USD через явно приблизительную таблицу FX, с оговоркой о размере выборки — никогда не выдумываются.
  • Фильтры по роли и стране и самодельные встроенные SVG-графики: столбчатые и трендовые (без новых зависимостей, безопасно для CSP — только addEventListener).
  • Сохранить снимок (POST /api/stats/snapshot) сохраняет текущий агрегат в data/role-stats.jsonl; график тренда (GET /api/stats/trend) отслеживает количество вакансий со временем — представление «динамики». Честный гибрид: снимки формируются из локальных данных сканирования и обновляются по запросу.
  • Полностью локализовано во всех 16 локалях (26 новых ключей i18n).

Новое: server/lib/routes/stats.mjs (16-й модуль маршрутов), public/js/lib/role-stats.js, public/js/views/stats.js, PATHS.roleStats; тесты role-stats.test.mjs (7) + stats-routes.test.mjs (5).

[1.85.0] — 2026-07-03

Локали немецкого, итальянского и турецкого (паритет локалей с родительским career-ops v1.16.0). Интерфейс теперь поставляется на 16 языках — de 🇩🇪, it 🇮🇹 и tr 🇹🇷 добавляются к уже имеющимся 13.

  • Полный перевод интерфейса — все 730 ключей i18n переведены в public/js/lib/locales/i18n-dict.{de,it,tr}.js; переключатель языков показывает Deutsch / Italiano / Türkçe, а автоопределение языка браузера распознаёт de/it/tr (public/js/lib/i18n.js).
  • Встроенное руководство помощи — docs/help/{de,it,tr}.md переведены (полная структура из 19 H2 / 75 H3), отдаются через GET /api/help/:lang.
  • Документация — добавлены README.{de,it,tr}.md и CHANGELOG.{de,it,tr}.md; проверка паритета локалей CHANGELOG теперь охватывает 15 не-EN локалей.
  • Каркас промптов — server/lib/prompts.mjs (LOCALE_NAMES + SCAFFOLD_STRINGS) локализован для трёх новых локалей, так что вывод LLM следует языку интерфейса.

Все проверки паритета (i18n-locale-files, i18n-coverage, check-changelog-parity, lang-switcher-rtl) расширены на набор из 16 локалей.

[1.84.0] — 2026-06-30

Кулдаун повторных заявок + компенсация в pipeline.md (паритет с родительским career-ops v1.15.0). Два улучшения сканера:

  • Кулдаун повторных заявок (#1201): EN-скан теперь пропускает роли в компаниях, в которые вы уже подавали заявку недавно, — результаты остаются сфокусированы на НОВЫХ вакансиях. Настройте временны́е окна на уровне компании в config/profile.yml в секции re_apply_windows: (last_apply_date, same_role_days, applied_to: [roles], необязательный cross_role_bucket); сопоставление компаний нечувствительно к пунктуации и работает по границам слов (server/lib/cooldown.mjs). Не действует, если ключ отсутствует; в лог сканирования выводится Cooldown skipped: N.
  • Компенсация в pipeline.md (#1017): найденные при сканировании вакансии теперь сохраняют зарплату в виде необязательного завершающего столбца (url | <salary>) в data/pipeline.md. URL остаётся ключом дедупликации (столбец | comp убирается при чтении), значение ячейки санируется (нет инъекции строки/столбца, формульный префикс нейтрализуется), а pipeline в формате «просто URL» остаётся обратно совместимым.

Поставляется с tests/cooldown.test.mjs и тестами компенсации для pipeline. Количество источников не изменилось — 41 (оба улучшения относятся к логике сканирования, а не к новым доскам).

[1.83.0] — 2026-06-30

Детектор повторных публикаций / фантомных вакансий (паритет с родительским career-ops v1.15.0). На странице #/scan появилась панель 🔁 Повторные публикации / фантомные вакансии, которая помечает кластеры «компания + должность», повторно размещённые под разными URL в скользящем 90-дневном окне — сигнал устаревших воронок и фантомных объявлений. Работает на основе нечёткого сравнения заголовков (server/lib/role-matcher.mjs) и read-only детектора (server/lib/detect-reposts.mjs) по data/scan-history.tsv, отдаётся через GET /api/scan/reposts. Также: parentVersion в /api/health теперь возвращает только semver (комментарий # x-release-please-version из release-please убирается). Поставляется с tests/detect-reposts.test.mjs. Количество источников не изменилось — 41: повторные публикации — аналитическая функция, не новая доска.

[1.82.0] — 2026-06-30

Источник сканирования NoDesk (паритет с родительским career-ops v1.15.0). Общедосочная RSS-лента удалённой работы NoDesk теперь полноценный источник — добавьте запись provider: nodesk, и она появится в выпадающем списке Source на #/scan (всего 41 адаптер: 36 EN + 5 RU). Хост закреплён за nodesk.co с redirect:'error' (защита от SSRF); заголовки делятся по Role at Company (у NoDesk нет тега локации, поле местоположения остаётся пустым); все вакансии — удалённые. Поставляется с CI-изолированным набором тестов tests/sources-nodesk.test.mjs; полный набор юнит-тестов зелёный, 1523.

[1.81.0] — 2026-06-29

Паритет с родительским career-ops — 13 новых источников сканирования вакансий. Переносит последнюю партию провайдеров из main Fighter90/career-ops во встроенный сканер. Общедоступные API досок (выбор по provider:): Arbeitnow, Himalayas, Jobicy, Landing.jobs, 4 Day Week, The Muse, The Hub, Jobspresso (RSS) и Hacker News “Who is hiring?” (Algolia, два шага). Польские доски (определяются по хосту или provider:): JustJoin.it и NoFluffJobs (POST-поиск). Per-tenant ATS (автоопределение по careers_url): Pinpoint (<slug>.pinpointhq.com/postings.json) и Rippling (ats.rippling.com/<slug> → api.rippling.com board). Каждый источник закреплён по хосту с redirect:'error' (защита от SSRF) и доступен в выпадающем списке Source на #/scan — реестр теперь содержит 40 адаптеров сканера (35 EN + 5 RU). Добавлены 13 изолированных CI-наборов тестов на источник; полный набор юнит-тестов зелёный, 1513.

[1.80.0] — 2026-06-28

Пять улучшений сканера (идеи из job-crawler, переписаны). (1) Источник Teamtailor — карьерные сайты <slug>.teamtailor.com через публичную ленту /jobs.rss, автоопределение по careers_url (хост закреплён + redirect:'error'); в реестре теперь 27 адаптеров. (2) Карантин источников — источник с постоянной 404/410 записывается в data/scan-quarantine.json и пропускается в следующих сканах (самовосстановление: повтор через 14 дней). (3) Макс. на источник — необязательное поле на #/scan, ограничивающее число вакансий с каждой доски (∞ по умолчанию). (4) Опубликовано за — клиентский фильтр по возрасту (24ч / 7д / 30д). (5) Сохранённые поиски + ★ избранное — называйте и переиспользуйте наборы фильтров и отмечайте вакансии; хранится в localStorage с защитной валидацией (повреждённый кэш сбрасывается чисто); кэш результатов сбрасывается перед сканом и наполняется вживую.

[1.79.0] — 2026-06-28

Источник сканирования WeWorkRemotely (паритет с career-ops v1.14.0). Общедосочная RSS-лента удалёнки We Work Remotely теперь полноценный источник — добавьте запись provider: weworkremotely, и она появится в списке Source на #/scan (26 адаптеров всего). Хост закреплён за weworkremotely.com с redirect:'error' (защита от SSRF); заголовки делятся по Company: Role. Также: ключевые слова title_filter теперь обрезаются до проверки длины (parent #1261).

[1.78.2] — 2026-06-27

Доработки i18n и UX (follow-up к v1.78.1). Доступное имя логотипа теперь локализовано во всех 13 языках (nav.logoHome). Enter в глобальном поиске, когда вы уже на #/scan, делает принудительный ре-рендер, чтобы введённый запрос не терялся (same-route guard). health.title переведён на польский (Kondycja) и датский (Systemtilstand) — раньше оставался по-английски. Тесты 1235 → 1238.

[1.78.1] — 2026-06-27

Исправления UX на странице сканирования. Таблица результатов #/scan теперь автоматически обновляется во время сканирования и ещё раз после его завершения — без перезагрузки. Глобальный поиск в шапке показывает подсказку Enter и при не-URL-запросе переходит на #/scan с заполненным полем поиска (раньше — #/tracker). Логотип теперь ведёт на дашборд (главную).

[1.78.0] — 2026-06-27

Гео-фильтр на странице сканирования — фильтр вакансий по стране, с флагами. Новый список Страна в #/scan показывает каждую страну, найденную в результатах (эмодзи-флаг + счётчик), чтобы оставить только вакансии, привязанные к конкретной стране — рядом с фильтром Remote/Hybrid/Onsite, так что можно искать как с привязкой к стране, так и удалёнку. Реализовано новым помощником countries.js, который сопоставляет свободный текст локации (названия стран, алиасы и ~100 крупных городов рынка труда) со страной ISO + флагом; детекция консервативна и не угадывает.

[1.77.0] — 2026-06-27

Добавлен датский (Dansk) — 13-й язык интерфейса. Полный перевод UI, встроенного руководства Help (19 H2 / 75 H3), README и CHANGELOG. Датский добавлен в переключатель языков с флагами; механика i18n (ассемблер, аудит, проверки паритета, снапшот) теперь охватывает 13 локалей.

[1.76.0] — 2026-06-26

Паритет с родительским career-ops v1.13.0 — шесть новых источников вакансий, усиление сканера и таблица результатов без лимита.

Добавлено

  • Шесть ATS-источников на тенант — BambooHR, Breezy HR, Comeet, Personio, Recruitee, SolidJobs. Определяются по хосту careers_url (для Comeet нужен полный api:-URL), хост закреплён якорным regex + redirect:'error' (защита от SSRF). Все доступны в выпадающем списке Source на #/scan — реестр теперь содержит 25 адаптеров (20 EN + 5 RU). Добавлен помощник fetchText для XML-ленты Personio.
  • trust_filter — опциональная оценка доверия (0–100, уровень high/medium/low, флаги), только аннотирует. Записи ниже high получают нейтральный значок ⚠ на #/scan; вакансии никогда не отбрасываются.
  • Arbeitsagentur remoteMatch + remoteMaxPages — определение удалёнки из конфига: title (regex), filter (серверный homeoffice=nv_true + пагинация) или off.

Изменено

  • Нет лимита результатов скана. Лимит MAX_STORED_RESULTS (2000) убран — хранятся все совпавшие вакансии, таблица #/scan листает их (200/стр.). Большие прогоны больше не теряют хвост.
  • Надёжность фильтра по заголовку — короткие аббревиатуры (COO, SDR…) теперь сопоставляются по границам слов; некорректный title_filter больше не роняет скан. Оба сканера (ATS и региональный).

Тесты

  • +32 кейса (1190 → 1222): sources-ats-providers, title-filter, arbeitsagentur-remote, trust-validator и переписанный страж scan-result-cap («без лимита»).

[1.75.2] — 2026-06-19

docs: полный паритет документации для агрегаторов сканера из v1.75.0 во всех 12 локалях. Без изменения кода — приводит пользовательскую документацию в соответствие с семью источниками, появившимися в v1.75.0:

  • Справочное руководство (12 локалей). В §5 добавлен блок content_filter (гейтинг по ключевым словам описания/сниппета, аналог location_filter) и примечание об агрегаторах; §7 перечисляет семь новых источников в проходе сканирования в один клик и в полном перечислении выпадающего списка Source; счётчик адаптеров в §17 исправлен с устаревших «11 adapters» на «19 adapters — 14 English + 5 Russian». Заголовков ##/### не добавлялось, поэтому закреплённая структура 19 H2 / 75 H3 осталась неизменной.
  • README (9 полных локалей). Новый пункт «Aggregator boards (v1.75.0)» под источниками сканирования, плюс значок релиза, обновлённый до v1.75.2. (У сокращённых README pl/uk/ar нет списка по источникам, и там они намеренно не тронуты.)
  • Справочные документы. В docs/portals-examples.md добавлен готовый к копированию раздел «Aggregator boards» с точными блоками конфигурации provider: / <provider>: для всех семи; docs/PROJECT.md обновлён до 19 adapters; docs/sdd/CONVENTIONS.md документирует различие двух реестров (sources/registry.mjs для выпадающего списка против portals/registry.mjs для выборки), выбор агрегатора на основе provider:, прокидываемый как opts.company, санитайзер записи сканирования (scan-sanitize.mjs) и число тестов v1.75.1 (1190).
  • QA. Добавлен qa/QA-REGRESSION-PROMPT-v1.75.2-FULL.md — драйвер релизного гейта по полной поверхности, обновлённый под цикл агрегаторов сканирования v1.75.x.

[1.75.1] — 2026-06-19

fix(scan): доводка надёжности источников на основе конфигурации из v1.75.0. Три небольших усиливающих исправления по итогам послерелизного разбора (без изменения поведения при штатном сканировании):

  • Паузы пагинации с учётом отмены. Межстраничные вежливые паузы Glints (300 мс) и Jobstreet/SEEK (200 мс) теперь немедленно разрешаются при срабатывании AbortSignal сканирования — через новый помощник delay(ms, signal) в server/lib/http-json.mjs, так что отключившийся клиент не может удерживать пагинируемое сканирование открытым ещё на одну паузу.
  • Описательная ошибка для не-JSON ответа. fetchJson теперь оборачивает не-JSON тело с кодом 2xx (например, HTML-страницу обслуживания, отданную со статусом 200) как non-JSON 2xx response from <url> вместо того, чтобы выдавать голый SyntaxError, так что журнал ошибок сканера по каждому источнику называет некорректно ведущий себя эндпоинт.
  • Усиленная нормализация записи сканирования. normalizeScanScalar теперь сворачивает вертикальную табуляцию, перевод страницы и разделители строки/абзаца Unicode (\v \f U+2028 U+2029) в дополнение к \r \n \t — строгое надмножество, так что ни один разделитель записи/строки, который могла бы учесть таблица или просмотрщик, не попадает в scan-history.tsv.

[1.75.0] — 2026-06-19

feat(scan): переносим паритет с родительским career-ops v1.12.0 — семь новых источников вакансий, фильтрация контента и исправления безопасности/качества. web-ui запускает собственные внутрипроцессные сканеры (он не вызывает родительский scan.mjs), поэтому изменения провайдеров и сканирования из родительской v1.12.0 не проходят автоматически — этот релиз переносит применимые из них в контракт адаптеров web-ui.

  • Семь новых источников сканера. Три глобальных агрегатора удалённых вакансий — RemoteOK, Remotive, Working Nomads — встраиваются в автоматически обнаруживаемый паттерн server/lib/sources/*.mjs (выбираются через provider: remoteok / remotive / workingnomads). Четыре региональных агрегатора, управляемых конфигурацией — careers IBM, Arbeitsagentur (Федеральное агентство занятости Германии), Glints (Юго-Восточная Азия), Jobstreet / SEEK — читают поэлементный конфигурационный блок <provider>:; en-scanner теперь прокидывает разрешённую запись компании в каждый фетчер, чтобы они могли её прочитать. Все семь автоматически появляются в выпадающем списке источников #/scan.
  • content_filter (родительский #974). Необязательный блок portals.yml (списки ключевых слов positive / negative), который фильтрует вакансию по тексту её описания/сниппета — повторяет семантику location_filter; вакансии без описания всегда проходят. Подключён в оба сканера — EN и RU.
  • Усиление записи при сканировании (родительский #1098). Метаданные внешних фидов теперь санируются перед попаданием в data/scan-history.tsv и data/pipeline.md: управляющие символы схлопываются (перенос строки в названии компании/вакансии больше не может внедрить строку TSV), а ведущие = + - @ нейтрализуются против инъекции формул в электронные таблицы.
  • secondaryLocations Ashby (родительский #1073). Источник Ashby теперь сворачивает метку региона каждой вторичной локации плюс почтовые addressLocality / addressCountry в строку локации (с дедупликацией), так что роль с правом работы в ЕС, чья основная метка читается, например, как “Canada”, всплывает для location_filter.
  • Валидация формы отчёта об оценке (родительский #819). Внутрипроцессные провайдеры /api/evaluate (Anthropic / OpenAI / Qwen / OpenRouter / GitHub Models) теперь помечают некорректный отчёт A–G / SCORE_SUMMARY как нефатальный массив warnings; путь оценки Gemini уже наследует эту защиту из родительского gemini-eval.mjs.
  • docs: Antigravity CLI добавлен в списки поддерживаемых ассистентов во всех 12 README (сопоставляется с провайдером Gemini).

Унаследовано бесплатно из git pull родителя (web-ui вызывает их через shell): запасной набор CJK-шрифтов для японского PDF (#1053), ATS-совместимые PDF-шрифты (#1074), защита CJK для LaTeX (#1054), исправления tracker/merge/followup/dashboard и китайские режимы modes/zh (web-ui перечисляет режимы динамически).


[1.74.3] — 2026-06-18

docs(parent-source): нацеливаем родительский репозиторий career-ops на форк Fighter90/career-ops. web-ui теперь ссылается на форк мейнтейнера как на родительский проект везде, где это живой источник: значение по умолчанию CAREER_OPS_REPO в установщике bin/setup.sh, каждая ссылка git clone / «поверх» / онбординга во всех 12 README и документация агентов (CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md, docs/). Указание авторства santifer (и дисклеймер о неофициальном UI) не изменилось — переместились только URL источника/клонирования. tests/sh-files.test.mjs теперь проверяет, что установщик клонирует форк.


[1.74.2] — 2026-06-17

fix(health): отображение GITHUB_MODELS_API_KEY как необязательной проверки на #/health и в /api/status/providers. Провайдер GitHub Models из v1.74.0 настраивался в #/config, но не имел строки на странице Health и отсутствовал в провайдерской поверхности keysConfigured. Добавлены необязательная проверка (с той же формулировкой “set / unset (manual mode)”, что и у остальных пяти провайдеров живой оценки) и github (+ его GITHUB_MODELS_MODEL) в /api/status/providers, поэтому маршрутизация активного провайдера и страница Health теперь отражают все шесть. Тест строки health в tests/api.test.mjs расширен на все шесть провайдеров.


[1.74.1] — 2026-06-17

docs + test: раздел README «Установка ИИ-ассистента»; полное покрытие ветвей для коннектора Gemini. В README добавлена таблица установки/входа — ссылки на установку Claude Code / Gemini CLI / Codex / Qwen Code / OpenCode / GitHub Copilot CLI + привязка провайдера #/config для каждого + «войдите в систему перед продолжением» (соответствует разделу быстрого старта career-ops.org/docs; поясняет, что web-ui является самостоятельной альтернативой, не требующей CLI). Новый tests/gemini-connector.test.mjs (8 сценариев) охватывает каждую ветку runGemini — отсутствие ключа, успех, ошибка API, пустое/заблокированное завершение, некорректное тело, тайм-аут, сетевая ошибка, hasGeminiKey — доводя server/lib/gemini.mjs до 100% по инструкциям. Общее покрытие: 96% строк / 88% ветвей / 96% функций. Набор тестов 1126 → 1134.


[1.74.0] — 2026-06-17

feat(llm): GitHub Models (Copilot) как 6-й провайдер + каноническое выравнивание 6 ассистентов. career-ops.org/docs перечисляет шесть ИИ-ассистентов для написания кода — Claude Code, Gemini CLI, Codex, Qwen Code, OpenCode, GitHub Copilot CLI. Теперь web-ui поддерживает все шесть: пять сопоставлены с существующими активными провайдерами (Anthropic / Gemini / OpenAI / Qwen / OpenRouter), а GitHub Copilot CLI получает выделенный коннектор GitHub Models — runGitHubModels (OpenAI-compatible; PAT GitHub с областью models), настраиваемый в #/config (GITHUB_MODELS_API_KEY + GITHUB_MODELS_MODEL) и выбираемый через LLM_PROVIDER=github; 6-й в порядке auto. Пакеты справки и README теперь перечисляют канонические шесть (переименован Qwen CLI→Qwen Code; добавлены Gemini CLI + GitHub Copilot CLI), а README добавляет полную таблицу ссылок на режимы и адаптеры порталов на career-ops.org/docs, чтобы каждая функция отслеживалась до родительского проекта. tests/llm-provider-context.test.mjs расширяет матрицу границ получения до всех шести провайдеров (cv.md + profile.yml встроены + возвращаемый артефакт); новые ключи GITHUB_MODELS_* добавлены во все 12 словарей локалей. Набор тестов 1125 → 1126.


[1.73.0] — 2026-06-17

feat(llm): универсальный коннектор Gemini + верифицированный контекст CV/профиля для всех провайдеров. Добавлен server/lib/gemini.mjs (runGemini) — клиент Gemini generateContent без внешних зависимостей, возвращающий ту же форму {markdown, usage, error}, что и клиенты, совместимые с Anthropic / OpenAI. Исправление: /api/mode/:slug и /api/deep ранее пропускали промпты через gemini-eval.mjs, предназначенный исключительно для оценки вакансий, из-за чего Gemini Run live возвращал оценку вместо запрошенного артефакта (сопроводительное письмо, аутрич, бриф). Теперь они вызывают runGemini с bundleProjectContext, поэтому cv.md + config/profile.yml встраиваются инлайн для Gemini точно так же, как и для всех остальных провайдеров — письма и брифы становятся подробными и персонализированными. Новый tests/llm-provider-context.test.mjs мокирует HTTP-границу каждого провайдера и проверяет, что все пять (Anthropic / Gemini / OpenAI / Qwen / OpenRouter) встраивают cv.md + profile.yml инлайн и возвращают артефакт (матрица mode + deep + evaluate, 9 случаев). /api/evaluate сохраняет свой gemini-eval.mjs, настроенный под вакансии. Suite 1116 → 1125.


[1.72.0] — 2026-06-17

feat(modes): Run live теперь возвращает итоговый артефакт напрямую (контракт вывода за один запрос). Родительские шаблоны modes/<slug>.md написаны для интерактивных сессий Claude Code — некоторые (cover, contacto и др.) делают паузу, чтобы задать уточняющие вопросы перед формированием результата, из-за чего Run live в веб-интерфейсе выдавал анкету вместо артефакта. buildModePrompt теперь оборачивает каждый режим в неинтерактивный контракт вывода: выполняет анализ (разбор вакансии, заметки о компании, ключевые слова ATS, расхождения профиля↔вакансии, выбор тона/подачи) в фоновом режиме, выбирает разумные значения по умолчанию из cv.md / config/profile.yml для всего, о чём шаблон обычно спрашивал, и выводит только итоговый артефакт — с завершающим напоминанием для каждого режима «output ONLY {the cover letter / outreach message / …}». Теперь клик на Run live в #/cover возвращает само сопроводительное письмо; то же исправление применяется ко всем универсальным режимам (cover, contacto, interview-prep, project, training, followup, patterns) во всех 12 локалях (артефакт создаётся на языке интерфейса через директиву локали). Suite 1103 → 1116.


[1.71.2] — 2026-06-17

docs(i18n): публикует проход по согласованности документации. Блок “Translations of this guide” в каждом README теперь перечисляет все 11 языков-«сестёр» (ранее в некоторых пропускались English/Français или присутствовала ссылка на самого себя), восстановлена пустая строка перед разделителем секций. Полный QA-промпт регрессионного тестирования переименован в текущую версию, а документация (CLAUDE.md, CONVENTIONS, LOCALIZATION, PROJECT-CONTEXT) синхронизирована с текущей версией и количеством тестов (1103). Изменений в коде или поведении нет — только документация, поэтому переводы справки/UI и все функции версий 1.70.0–1.71.1 остаются без изменений.


[1.71.1] — 2026-06-17

fix(i18n): встроенное руководство по помощи теперь полностью переведено на все 12 языков. Добавлены docs/help/{pl,uk,ar}.md (каждый содержит проверенную структуру 19 H2 / 75 H3), благодаря чему #/help отдаёт нативный пакет на польском, украинском и арабском языках вместо перехода на английский — GET /api/help/{pl,uk,ar} теперь возвращают собственную локаль. Подключено ко всем проверкам помощи (help-ui, help.test, help-ru-config-section, canonical-docs-coverage). Также завершены все списки переводов на 12 языков: блок «Translations of this guide» в README (9 README), заголовки «Translations:» в локализованных CHANGELOG (8 файлов), а также обновлены устаревшие счётчики документации. Suite 1100 → 1103.


[1.71.0] — 2026-06-16

feat(cover): генерация PDF сопроводительного письма прямо из #/cover. Режим cover (добавлен в v1.70.0) формирует текст письма; теперь в результате появляется кнопка Generate PDF, которая рендерит его через общий встроенный конвейер markdown→PDF (POST /api/stream/pdf/inline → generate-pdf.mjs) — тот же путь, что использует interview-prep. Теперь можно создать письмо и отправить PDF, не покидая SPA.

test/docs: усиление ревью v1.70.0. Добавлено CI-изолированное покрытие для режима cover (список разрешений + сборка промпта), переключателя <select> флагов + арабского RTL (dirFor/<html dir>), top.langLabel в каждой локали, подключения PDF сопроводительного письма, а также директивы локали prompts.mjs + скаффолдинга для fr/pl/uk/ar. Обновлены устаревшие упоминания «всех 8» → 12 локалей в docs/sdd/CONVENTIONS.md и в промпте регрессионного QA всего проекта.


[1.70.0] — 2026-06-16

feat(i18n): три новых языка интерфейса — польский (pl), украинский (uk) и арабский (ar, с полной поддержкой RTL) — расширяют SPA до 12 локалей, охватывая все языки из README родительского проекта career-ops. Каждая новая локаль поставляется с полным словарём из 697 ключей (public/js/lib/locales/i18n-dict.{pl,uk,ar}.js), проверенным существующими наборами тестов на паритет / покрытие / отсутствие латинских утечек / отсутствие персональных данных. Арабский язык добавляет полноценную поддержку письма справа налево: i18n.js устанавливает <html dir="rtl"> для RTL-локалей, а блок [dir="rtl"] в app.css отражает хром (боковая панель, ящик уведомлений, таблицы и блок-цитаты в markdown, встроенные отступы) — LTR-локали остаются байт в байт без изменений. Новый ключ top.langLabel (×12) именует переключатель для программ чтения с экрана.

feat(ui): выпадающий список <select> с иконками флагов заменяет ряд переносящихся кнопок. С 12 локалями прежний ряд .lang-btn переносился на три строки в боковой панели; нативный <select> (каждый пункт с префиксом в виде эмодзи флага) компактно масштабируется, изначально поддерживает клавиатуру и экранные считыватели, а также безопасен для CSP (обработчик изменений через addEventListener, без встроенного JS). Там, где платформа не поддерживает глифы флагов, они заменяются буквами региона, поэтому языковая метка всегда остаётся основным идентификатором.

feat(cover): перенос режима сопроводительного письма из родительского проекта (career-ops v1.10.0 + приветствие v1.11.0) в SPA. Новая страница #/cover в группе навигации «Заявки», построенная на базовом исполнителе режимов: описание вакансии + компания/роль + необязательное приветствие → персонализированное письмо, сгенерированное из cv.md / modes/_profile.md. Добавлен cover в серверный MODE_ALLOWLIST и блок i18n cover.* (×12 локалей).

chore(compat): отслеживание родительского career-ops v1.11.0. Подтверждено, что контракт чтения/записи остаётся в силе — data/applications.md по-прежнему является markdown-источником истины (SQLite-индекс трекера в v1.11.0 — производный кэш), столбцы трекера по-прежнему сопоставляются по заголовку. parentVersion теперь сообщает 1.11.0.

fix(i18n): устранена скрытая проблема, при которой французский язык (добавленный в v1.61.0) отсутствовал в LOCALE_NAMES и SCAFFOLD_STRINGS в server/lib/prompts.mjs — LLM-вызовы на французском языке молча переходили на английский вывод и английские заготовки. fr/pl/uk/ar теперь все подключены к пути локали подсказок.

Известные доработки: встроенный справочник (docs/help/) откатывается на английский для pl/uk/ar (сам UI-хром полностью локализован); интерактивное собеседование-онбординг родительского проекта, обратный поиск ATS и новые провайдеры сканирования пока не представлены в SPA.


[1.69.2] — 2026-06-12

fix(test): закрыта утечка тест-изоляции, из-за которой npm test перезаписывал реальные config/profile.yml и data/scan-history.tsv. tests/critical-fixes.test.mjs импортировал prompts.mjs (→ paths.mjs) в начале файла, поэтому PROJECT_ROOT разрешался в реальный родительский каталог до того, как before() задавал CAREER_OPS_ROOT на временный — и PUT /api/profile на каждом прогоне записывал фикстуру «Acceptance Test» в реальный профиль. Исправление: загружать prompts.mjs через динамический import() внутри before(). Новый tests/test-root-isolation.test.mjs (2 кейса) защищает весь сьют от этого паттерна. Без изменений production-кода. Сьют 1084 → 1086.


[1.69.1] — 2026-06-12

fix(scan): #/scan больше не обрезает молча крупные региональные сборы. Отображаемый набор по региону был жёстко ограничен 500 (реальный RU-скан из 1352 подходящих вакансий показывал только 500; 852 скрыты — симптом «просканировано 2000, показано ~600»). Оба сканера теперь используют общую, переопределяемую через окружение константу MAX_STORED_RESULTS (по умолчанию 2000, переопределение через SCAN_MAX_RESULTS). Только отображение — добавление в pipeline.md / scan-history.tsv и так использовало необрезанный набор. fix(health/ui): карточки проверок #/health больше не вылезают за пределы. Длинное имя/значение сталкивалось с кнопкой Fix → и бейджем статуса; строка теперь сжимается и переносится через .health-check-row. Новые тесты scan-result-cap + health-card-overflow. Набор 1079 → 1084.


[1.69.0] — 2026-06-12

feat(scan): автообнаружение адаптеров сканера (P-14) — достаточно положить .mjs в server/lib/sources/, чтобы зарегистрировать новый источник. До v1.69 список источников в server/lib/sources/registry.mjs был статическим массивом, поддерживаемым вручную: добавление адаптера требовало правок и в <id>.mjs, и в registry.mjs. Закрывает оставшуюся половину пункта P-14 дорожной карты (docs/ROADMAP.md). Теперь каждый *.mjs из server/lib/sources/ подгружается динамически при загрузке модуля; каждый адаптер заявляет о себе самоописательным блоком export const meta = { value, label, region, configKey? }. Все 12 поставляемых адаптеров (ashby / greenhouse / lever / rss / smartrecruiters / workable / workday + geekjob / getmatch / habr / hh / trudvsem) получили meta; registry.mjs использует readdirSync + динамический import() с top-level await (стандарт ESM Node 18+). Публичный API (SOURCES, SOURCES_BY_REGION, RU_CONFIG_KEYS, getRegionalSources) не меняется: все существующие импорты продолжают работать. Валидация отбрасывает некорректные meta и пишет console.warn на каждый сбойный файл. Новый tests/sources-registry-discovery.test.mjs с 14 кейсами. Сьют 1065 → 1079.


[1.68.2] — 2026-06-07

fix(bin): команды CLI через npx / npm link были сломаны — путь к bin теперь резолвится через симлинки. npm и npx публикуют career-ops-ui как симлинк в node_modules/.bin/, и старый dirname "${BASH_SOURCE[0]}" указывал на .bin, а не на корень пакета — поэтому npx career-ops-ui init запускал node node_modules/scripts/init.mjs и падал с MODULE_NOT_FOUND (локальный запуск после npm install работал, что и скрывало баг). Теперь bin/career-ops-ui.sh и bin/start.sh канонизируют SCRIPT_DIR по цепочке симлинков (цикл readlink + cd -P), так что любая команда работает из репозитория, через npm link и через npx. Добавлен регресс-замок в tests/sh-files.test.mjs, прогоняющий команду через симлинк в стиле .bin. Сьют 1065/1065.


[1.68.1] — 2026-05-29

fix(scan): таймаут запроса к источнику 10s → 60s. Fail-fast в 10s (v1.67.1) обрезал и «медленные, но живые» доски Ashby, которым просто нужно было больше времени. Поднимаем дефолт до одной минуты, чтобы они успевали ответить. Компромисс: реально мёртвый/зависший источник теперь держит слот параллелизма все 60s (худший случай скана медленнее), а хронически висящие (Perplexity, Supabase, Resend, …), скорее всего, всё равно отвалятся по таймауту — по-настоящему это решит фикс по источнику / снижение параллелизма для Ashby. Переопределяется SCAN_FETCH_TIMEOUT_MS. Сьют 1063/1063.


[1.68.0] — 2026-05-29

feat(scan): переработана панель фильтров результатов — поля с подписями, кнопка «Применить», вариант «В офисе» и работающий фильтр по зарплате. Каждый фильтр на #/scan теперь поле с подписью (подпись над контролом, а не плейсхолдер): Поиск · Формат · Зарплата от · Зарплата до · Источник · Объём. Явная кнопка «Применить» (плюс «Сбросить» и Enter в любом поле) перезапускает фильтр; подсказка на странице объясняет, как это работает. Фильтр по зарплате теперь действительно фильтрует — как только задано значение от/до, вакансии с оплатой вне диапазона и вакансии без указанной зарплаты убираются (пересечение диапазонов; валюта не учитывается). В фильтр формата добавлен вариант «В офисе» рядом с Remote / Hybrid / Релокация. Новые ключи i18n ×9; salaryInRange сделан строгим; сьют 1063/1063.


[1.67.1] — 2026-05-29

fix(scan): таймаут запроса к источнику 30s → 10s (fail-fast). Поднятие до 30s в v1.67.0 вернуло лишь ~половину медленных досок Ashby; остальные (Perplexity, Supabase, Resend, DeepL, Ramp, …) висят независимо от дедлайна, поэтому больший таймаут лишь тормозил весь скан, ожидая мёртвые слоты. 10s быстро отбраковывает хронически висящие источники и сохраняет отзывчивость скана. Переопределяется SCAN_FETCH_TIMEOUT_MS. Сьют 1060/1060.


[1.67.0] — 2026-05-29

feat(scan): фильтр по зарплате (от / до) на #/scan и увеличенный таймаут запросов к источникам. В таблице результатов рядом с фильтрами по тексту и remote появились два числовых поля — зарплата от / до. Зарплата каждой строки в свободном тексте (от 100 000 до 200 000 ₽, 120000-150000 USD, $120K–$150K, …) парсится в числовой диапазон и сопоставляется по пересечению диапазонов; строки без указанной зарплаты сохраняются, поэтому фильтр сужает список, а не обнуляет его (сравнение без учёта валюты — без конвертации курса). Также поднят таймаут запроса к источнику с 15s → 30s (переопределяется SCAN_FETCH_TIMEOUT_MS): payload Ashby с includeCompensation под параллелизмом ×8 регулярно занимал >15s, из-за чего ~30 досок Ashby отваливались по таймауту в каждом скане. Новые window.Skills.parseSalaryRange/salaryInRange + i18n ×9; 13 новых тестов; сьют 1060/1060.


[1.66.0] — 2026-05-28

feat(scan): РФ-источники теперь обходят ВСЕ страницы, а не только первую. hh.ru, Habr Career и Trudvsem брали только первые ~50 вакансий на запрос; теперь идут по пагинации до конца — &page=N для hh.ru/Habr, offset/meta.total для Trudvsem — с дедупом между страницами и остановкой, когда страница не даёт ничего нового (или по предохранителю в 50 страниц). Запрос вроде «Backend разработчик» теперь возвращает весь результат, а не одну страницу (напр. hh.ru PHP 17 → 55+ за 3 страницы; Trudvsem отдаёт все 72). Постраничные запросы сохраняют существующий таймаут + AbortSignal. 4 новых теста; сьют 1045/1045.


[1.65.0] — 2026-05-28

feat(scan): hh.ru теперь парсится с публичного сайта вместо JSON-API — работает с любого IP, без прокси. api.hh.ru стал отдавать голый 403 forbidden любому программному клиенту независимо от IP (US, РФ-датацентр, РФ-residential, РФ-мобильный) и User-Agent — это анти-бот на периметре, а не задокументированная ошибка API. Зато сайт (hh.ru/search/vacancy) отдаёт полные серверно-отрендеренные результаты любому браузероподобному клиенту, поэтому адаптер теперь парсит его HTML (как Habr Career). Удаляет переменную HH_PROXY из 1.64.0 и зависимость undici — больше не нужны ни прокси, ни ключ, ни настройка User-Agent. Тесты переписаны под HTML-парсер; сьют 1041/1041.


[1.64.0] — 2026-05-27

feat(scan): запрос к hh.ru через российский прокси по HH_PROXY. hh.ru блокирует свой API по IP, а не по User-Agent — поэтому один HH_USER_AGENT никогда не снимал 403 с зарубежного выходного узла. Укажи в HH_PROXY URL российского HTTP/HTTPS-прокси (напр. http://user:pass@ru-host:port) — через него пойдёт только запрос к hh.ru, остальные источники сохраняют прямое соединение. Реализовано на ProxyAgent из undici (новая прод-зависимость); при пустом HH_PROXY диспетчер не добавляется вовсе, изменённое значение подхватывается после перезапуска. 3 новых теста; сьют 1041/1041.


[1.63.2] — 2026-05-27

feat(scan): живой % прогресса + детали по источникам в консоли #/scan. Прогресс-бар стал детерминированным — сканеры шлют события прогресса (EN: по компаниям; RU: по запросам) через SSE, бар заполняется с живой подписью «Scanning… NN%» (анимированная полоса — только до первого события). Первая ошибка каждого источника (таймаут / 403 / сеть) теперь детально логируется в консоль (напр. ⚠ hh timed out: …), повторы подавляются. 1 новый тест; сьют 1040/1040.


[1.63.1] — 2026-05-27

style(scan): прогресс-бар на #/scan стал заметнее. Индикатор обёрнут видимой подписью «Scanning…», высота бара увеличена до 8px (было тонкие 4px) — теперь явно виден во время скана. Поведение не изменилось.


[1.63.0] — 2026-05-27

feat(scan): таймаут на запрос + прогресс-бар на #/scan. У запросов источников не было дедлайна, поэтому зависший источник (например, api.hh.ru с заблокированного IP) мог подвесить весь скан. Новый server/lib/fetch-timeout.mjs оборачивает fetchImpl сканеров (makeTimeoutFetch, по умолчанию 15с, переопределяется SCAN_FETCH_TIMEOUT_MS) — у каждого запроса жёсткий дедлайн; истёкший по таймауту источник записывается как некритичная ошибка, скан продолжается (Habr работает, даже когда hh.ru недоступен). На #/scan во время скана показывается индикатор прогресса (ключ scan.progress во всех 9 локалях). 7 новых тестов; сьют 1039/1039.


[1.62.3] — 2026-05-27

docs: прояснена установка (career-ops-ui работает внутри career-ops/web-ui/) + troubleshooting init, во всех 9 локалях. Install-секция переписана на Option 1 (один curl) / Option 2 (клонировать UI внутрь существующего career-ops как web-ui) + CLI-команды + настройка провайдера + блок Troubleshooting init — устранена частая путаница: клонируют career-ops-ui отдельно и запускают init без родительского проекта. Заметка о вложенной структуре добавлена и в /help §1 Setup; в README highlight — сводка всей линейки v1.62.*. Только документация; код не менялся.


[1.62.2] — 2026-05-27

fix(help): фильтр на #/help теперь полнотекстовый (находит H3-подсекции вроде RSS). Поиск/TOC-фильтр страницы help раньше искал только по заголовкам H2, поэтому документация по RSS из v1.62.x (H3 под §5 Portals & sources) не находилась. Теперь в фильтр индексируется и тело каждой секции, так что поиск, например, «RSS» показывает §5. Чисто клиентская правка, без изменений API.


[1.62.1] — 2026-05-27

feat(scan): RSS в фильтре источников + фикс локации RSS. Выпадающий фильтр источников на #/scan теперь содержит RSS (добавлен в server/lib/sources/registry.mjs + fallback-список SPA), поэтому результаты RSS-бордов (LaraJobs, WeWorkRemotely, …) фильтруются как любой ATS-источник. RSS-адаптер больше не кладёт тег <category> фида в location — не-локационные теги там заставляли location_filter ошибочно отбрасывать remote-вакансии; теперь location пустой и фиды проходят фильтр локации. Подписи/тултипы кнопки скана и строка списка источников (dash.quick.scanSub) обновлены во всех 9 локалях (Workable / SmartRecruiters / Workday / RSS). Обновлены i18n-снэпшот и тест эндпоинта источников (6 → 7 EN).


[1.62.0] — 2026-05-27

feat(scan): универсальный RSS-адаптер для не-ATS джоб-бордов. Новый адаптер rss (server/lib/portals/adapters/rss.mjs + server/lib/sources/rss.mjs) позволяет сканеру собирать вакансии из любого RSS-фида — LaraJobs, WeWorkRemotely, RemoteOK, golangprojects и других бордов вне Greenhouse/Ashby/Lever. Без новых зависимостей: парсинг фида на регулярках с поддержкой CDATA и HTML-сущностей (заголовки/компании очищаются от тегов, astral-символы декодируются безопасно). Включается для компании через provider: rss / rss: / feed_url: в portals.yml, поэтому не перехватывает компании, уже сопоставленные с ATS. ALL_ADAPTERS растёт 6 → 7. 29 новых тестов; описано во всех 9 локалях README.


[1.61.1] — 2026-05-22

fix(i18n): локализована подсказка (title) и aria-label кнопки переключения темы во всех 9 локалях (MINOR-001). Кнопка светлой/тёмной темы (#theme-toggle) имела захардкоженные title="Toggle theme" и aria-label="Toggle theme" в index.html — всплывающая подсказка и текст для скринридеров не переводились ни в одной локали. Новый ключ top.themeToggle + обработчик data-i18n-title в applyI18n() (по образцу исправления aria-label поиска в v1.58.15) локализуют оба атрибута при загрузке и при каждой смене языка. Заблокировано tests/playwright-theme-toggle-i18n.mjs (9 локалей + смена в рантайме) и двумя статическими guard-тестами. Единственная находка уровня LOW из sign-off v1.61.0. (MINOR-001)


[1.61.0] — 2026-05-22

feat(i18n): добавлен французский как 9-й язык интерфейса. Новый словарь по локали public/js/lib/locales/i18n-dict.fr.js (window.__I18N_DICT_FR) с полным паритетом 668 ключей с английским; новый пакет справки docs/help/fr.md (19 H2 / 73 H3, точный структурный паритет с en). fr зарегистрирован в переключателе языка и автоопределении браузера (i18n.js), в ассемблере (i18n-dict.js), в index.html (тег <script> перед ассемблером), в снапшоте тестов и во всех списках локалей тестов. Исходная таблица перевода — из PR #9 (вклад сообщества). Без изменений логики: t() и все представления неизменны. 1001 / 1001 модульных тестов; прогон локалей Playwright расширен до 9 подтестов. (FR-LOCALE)



career-ops-ui v1.267.1 · page 8/13 · mirror of the project changelog for cvstart.org