Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

295 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Генератор заявок для УО

License: AGPL-3.0-only CI Release TypeScript strict Coverage

Небольшое веб-приложение превращает обычное сообщение жителя многоквартирного дома в компактную, но достаточную заявку для управляющей организации. Пользователь описывает проблему своими словами, при необходимости указывает известные последствия и желаемые действия. Для двери, освещения, уборки помещения общего пользования, кровли МКД, вентиляции или лифта он отдельно подтверждает предмет проблемы, получает готовый текст и копирует его в удобный канал подачи.

Текущий статус

Репозиторий содержит рабочий модульный монолит. Форма, HTTP API и универсальный OpenAiCompatibleGateway реализованы. Yandex AI — одна из поддерживаемых конфигураций этого gateway, а не отдельный транспорт.

LLM возвращает внутренний JSON-черновик, который приложение строго валидирует и детерминированно преобразует в готовую заявку с полями title, body и warnings. Перед разделом Прошу: приложение обычным кодом вставляет в body два заранее проверенных общих нормативных основания и, при подтверждённой применимости, один предметный нормативный абзац для входной двери МКД, двери, освещения, уборки помещения общего пользования, кровли МКД, вентиляции общего имущества или лифта. Модули хранятся в Git/core, а backend подключает один из них fail closed только при явном пользовательском подтверждении предмета и точном сопоставлении provenance evidence с исходным вводом. Само evidence не доказывает смысловую применимость. Абзацы разделены пустой строкой и не содержат URL. Раздел Прошу: завершает основную часть заявки. LLM не получает тексты и ссылки на нормы или идентификатор модуля, не подбирает законодательство и не формирует индивидуальное юридическое заключение. Невалидный или частичный ответ модели не показывается как готовая заявка. Контракт и официальные источники описаны в docs/REQUEST_RULES.md.

LLM одним вызовом возвращает структурированный черновик: outcome, заголовок, описание проблемы, существенные обстоятельства, последствия и риски, проверяемый предмет и предупреждения. Provider response schema не зависит от наличия desiredActions.

Backend самостоятельно формирует ровно один пункт в разделе Прошу:. Без desiredActions это Устранить наблюдаемую проблему. Явное пользовательское требование сохраняется целиком после минимальной нормализации представления и не дополняется общим пунктом. LLM не сегментирует его, не переписывает и не формирует конкретный ремонт в разделе требований. Риск неподтверждённой конкретики в генеративном описании проверяется отдельно. Успешная ветка строго проверяется по provider-independent контракту из packages/core и передаётся детерминированному renderer. Подробные правила зафиксированы в docs/REQUEST_RULES.md. При нескольких несвязанных проблемах модель возвращает отдельный outcome без частичной заявки.

Коннектор поддерживает совместимые подмножества Chat Completions и Responses API. Для Chat Completions текст заявки читается из choices[0].message.content. Для Responses API gateway нормализует Yandex-compatible и стандартный сырой HTTP-ответ без SDK провайдера. Упрощённый Yandex-compatible ответ с непустым output_text может не содержать status, а стандартный вложенный ответ принимается только при status: completed.

Настройка LLM-провайдера

Создайте файл .env в корне проекта на основе .env.example.

Для production выбрана YandexGPT 5.1 Pro через Responses с обязательным явным LLM_MODEL в закрытой конфигурации. Полный идентификатор модели не публикуется. Production runbook задаёт настройки, условия допуска и rollout/rollback. Выбор модели в репозитории не подтверждает фактическое переключение: его операторские доказательства и решение о переносе квалификации #206 фиксируются в #229.

Протокол выбирается через LLM_API_PROTOCOL. Поддерживаются значения chat-completions и responses. Для обратной совместимости отсутствие переменной означает chat-completions, но в новых конфигурациях её следует указывать явно. URL и имя модели не используются для определения протокола.

Совместимая локальная конфигурация Yandex AI без явного LLM_MODEL использует endpoint https://ai.api.cloud.yandex.net/v1/chat/completions и модель YandexGPT для Chat Completions. Для Responses API используются endpoint https://ai.api.cloud.yandex.net/v1/responses и Alice AI LLM Flash. Эти значения по умолчанию сохранены для совместимости и не выбирают production-модель. Production использует явные параметры из .env.production.example, а не эти значения. Произвольный OpenAI-compatible провайдер настраивается переменными LLM_API_URL, LLM_API_KEY, LLM_AUTH_SCHEME, LLM_MODEL, LLM_PROVIDER и выбранным LLM_API_PROTOCOL. Для встроенной Yandex-конфигурации и явного LLM_PROVIDER=yandex приложение передаёт запрет логирования запросов. Его область действия и rollout-условие описаны в production runbook. Выбранные endpoint и модель должны поддерживать Structured Outputs: для chat-completions — через response_format с типом json_schema, для responses — через text.format с типом json_schema. Оба протокола используют одну выбранную строгую JSON Schema с strict: true. Fallback на свободный текст, json_object или повторный запрос отсутствует, поэтому провайдер без этой поддержки несовместим с текущим gateway.

HOST и PORT по умолчанию равны 0.0.0.0 и 3000. Обычно их менять не нужно.

Полный практический шаблон и пометки обязательных/опциональных переменных смотрите в .env.example.

Полное отсутствие всех поддерживаемых LLM_*-переменных включает поддержанный локальный development/diagnostic режим с DisabledLlmGateway, где API возвращает контролируемый ответ 503 без фиктивной заявки. Если задана хотя бы одна поддерживаемая LLM_*-переменная, конфигурация должна быть полной и валидной, иначе запуск завершается ошибкой конфигурации.

Защита частоты генераций

После успешной серверной валидации POST /api/generate проверяет внутрипроцессные лимиты до обращения к LlmGateway. По умолчанию один IP-адрес может выполнить не более 3 допущенных попыток за скользящие 60 секунд, а один браузерный клиент — не более 20 допущенных попыток за календарные сутки UTC. Одновременно для одного браузерного клиента выполняется не более одной генерации.

Браузерный клиент определяется случайным непрозрачным UUID в подписанной HTTP-only cookie uo_generation_client. Cookie не содержит пользовательские данные и передаётся только на /api/generate. Её Max-Age заканчивается на следующей границе календарных суток UTC, которая сбрасывает дневной limiter. При миграции действующего широкого варианта Path=/ приложение сохраняет тот же UUID на scoped path и удаляет legacy-cookie. При отсутствующем, неподписанном или повреждённом значении приложение подготавливает новый UUID, но устанавливает его только после допуска запроса limiter. Отклонённый запрос не получает replacement cookie. Для HTTP-сценария локальной разработки cookie работает без атрибута Secure, а для HTTPS-запроса получает его автоматически.

Установленный клиент занимает active-слот по собственному UUID. Для запроса без валидной cookie применяется консервативная временная проверка: пока с того же штатного request.ip выполняется любая генерация, новый запрос отклоняется. Разные установленные клиенты за одним IP могут выполнять генерации параллельно.

Лимиты настраиваются переменными GENERATION_IP_REQUEST_LIMIT, GENERATION_IP_WINDOW_MS, GENERATION_CLIENT_DAILY_LIMIT и GENERATION_RATE_LIMIT_STATE_CAPACITY. Доверие к reverse proxy выключено по умолчанию: без GENERATION_TRUSTED_PROXIES Fastify получает trustProxy: false. Переменная принимает разделённый запятыми allowlist буквальных IPv4, IPv6 и CIDR, например 192.0.2.10,198.51.100.0/24,2001:db8::10,2001:db8:1::/64. DNS-имя, *, число hop, пустой элемент и некорректный адрес останавливают запуск. Приложение использует штатные request.ip и request.protocol Fastify и не разбирает forwarding-заголовки самостоятельно.

В allowlist нужно указывать только фактические адреса или сети доверенных proxy и обновлять его при любом изменении proxy-цепочки. Backend не должен быть доступен публичному клиенту в обход proxy, а proxy обязан перезаписывать X-Forwarded-For и X-Forwarded-Proto данными фактического соединения, а не сохранять пользовательские значения. Настройка приложения не заменяет сетевое ограничение доступа к backend.

Для миграции удалите legacy-переменную GENERATION_TRUST_PROXY и задайте явный allowlist в GENERATION_TRUSTED_PROXIES. Наличие старой переменной считается ошибкой конфигурации и останавливает запуск.

Для подключённого LLM обязателен GENERATION_CLIENT_COOKIE_SECRET длиной не менее 32 символов. Отсутствующая или некорректная конфигурация защиты не запускает приложение с реальным gateway. При отключённом gateway локальный запуск остаётся доступен без секрета: используется случайный эфемерный секрет процесса.

Отказ любого клиентского лимита возвращает 429 с публичным кодом rate_limit_exceeded. Retry-After передаётся только для срока скользящего IP-окна или до следующей границы суток UTC, когда срок можно вычислить точно.

Общий предохранитель LLM-вызовов

После успешной CAPTCHA и непосредственно перед gateway приложение проверяет общий предохранитель. Для подключённого LLM необходимо явно задать GENERATION_ENABLED=true, GENERATION_GLOBAL_DAILY_LIMIT и GENERATION_GLOBAL_CONCURRENCY_LIMIT. Оба лимита принимают только положительные безопасные целые числа. GENERATION_ENABLED=false аварийно запрещает новые вызовы. Неполная или некорректная конфигурация не запускает реальный gateway. Локальный zero-config запуск с DisabledLlmGateway остаётся доступным.

Дневной лимит выбирают по худшей допустимой стоимости одной генерации:

дневной лимит =
допустимый расход за сутки /
максимальная допустимая стоимость одной генерации

Предохранитель ограничивает массовый расход, но не является точным финансовым бюджетом. Значения не отражают provider usage и не заменяют операторские отчёты. Отключение, исчерпанный дневной предел и занятая общая ёмкость возвращают один ответ 503 generation_unavailable без значений лимитов и внутренней причины. При отключении маршрут завершает прямой запрос до предметной валидации, подготовки browser client ID, limiter, CAPTCHA и consuming admission. Сохраняются только существующие безопасные requestId и metadata-only technical events.

Счётчики находятся только в одном процессе, ограничены по размеру и сбрасываются при перезапуске. Redis и распределённая синхронизация не используются, поэтому несколько экземпляров приложения не имеют общего лимита. Limiter хранит только IP-ключи, технические идентификаторы, временные отметки и счётчики без пользовательских текстов.

Проверка SmartCaptcha

Режим SmartCaptcha задаётся через SMARTCAPTCHA_MODE: значение disabled оставляет локальный сценарий без CAPTCHA, а required требует одновременно SMARTCAPTCHA_CLIENT_KEY и SMARTCAPTCHA_SERVER_KEY. Для подключённого LLM режим должен быть указан явно. Отсутствие переменной допускается только при DisabledLlmGateway, чтобы документированный локальный запуск без ключей оставался рабочим.

Браузер получает узкую публичную конфигурацию через GET /api/captcha/config. Она всегда содержит безопасный boolean generationAvailable, который не раскрывает причину недоступности или значения лимитов. Отключённый CAPTCHA-режим дополнительно возвращает {"required":false}, обязательный — required и публичный клиентский ключ. Серверный ключ не включается в этот ответ, HTML или browser JavaScript.

При generationAvailable=false browser показывает нейтральное сообщение о недоступности и не загружает script SmartCaptcha, не создаёт виджет и token, не отправляет форму в POST /api/generate. При доступной генерации существующий поток CAPTCHA остаётся без изменений.

Значение configuration фиксируется на время жизни загруженной страницы. Независимый server guard остаётся источником истины для прямых запросов и вкладок с устаревшим snapshot.

В обязательном режиме browser JavaScript динамически загружает официальный скрипт SmartCaptcha расширенным методом, создаёт невидимый виджет, скрывает стандартный shield и показывает встроенное уведомление об обработке данных. CAPTCHA запускается только после успешной клиентской валидации. Один непустой callback-токен создаёт один запрос генерации, после любого результата виджет сбрасывается, а следующая попытка требует новый токен. Некорректная публичная конфигурация или ошибка загрузки скрипта не приводит к незащищённому запросу.

Сервер отделяет captchaToken от предметного ввода на HTTP-границе. После успешного допуска limiter он отправляет токен, серверный ключ и штатный request.ip в SmartCaptcha как application/x-www-form-urlencoded. Timeout, сетевая ошибка, non-2xx, слишком большой или некорректный ответ и неоднозначный status: "ok" с пустым host трактуются fail closed. Автоматических повторов нет. status: "failed" возвращает 400 captcha_failed, техническая недоступность — 503 captcha_unavailable. Ни один отказ CAPTCHA не вызывает LlmGateway.

Репозиторий содержит только программную интеграцию и тестовые заглушки. Создание реального ресурса, установка ключей, публичное включение и ручная сквозная проверка относятся к issue #62. Если перед приложением используется reverse proxy, до публичного включения необходимо настроить его allowlist, перезапись forwarding-заголовков и сетевое ограничение доступа к backend.

Согласие Ц1

Форма требует отдельное согласие версии c1-2026-09-15-r1 для подготовки и возврата одной заявки. После допустимого CAPTCHA-потока backend проверяет принятие и точную версию, записывает долговечный receipt до LLM и возвращает x-consent-receipt-id, включая downstream failure. UI показывает код для поддержки и отзыва через [email protected]. Код не подтверждает личность, публичного lookup нет. Ц2–Ц4 не зависят от этого согласия.

Для запуска без контейнера с подключённым LLM задайте CONSENT_LEDGER_FILE для закрытого постоянного файла. В Compose используется отдельный named volume, который нужно сохранить при замене контейнера. Application-managed backups не создаются. Операторский срок evidence — три года с получения либо подтверждённого отзыва, с конкретными исключениями. Текст, SQLite, локальное обслуживание, подтверждение уничтожения и необходимые проверки до production описаны в docs/C1_CONSENT.md.

Структура

  • apps/web — Fastify-сервер, JSON API и статический интерфейс
  • packages/core — доменные схемы, типы и порт генерации
  • packages/llm — адаптеры порта генерации (DisabledLlmGateway, OpenAiCompatibleGateway)
  • docs — продуктовые правила, архитектура и ADR
  • scripts — проверки, общие для репозитория

Production deployment

Production Compose, первичная установка, диагностика и ручной rollback описаны в едином production runtime runbook.

После merge в main независимые CI jobs quality, test, build и browser проверяют один commit. Если все проверки успешны, workflow публикует образ ghcr.io/ashikov/uo-request-generator с полным commit SHA в качестве tag по соглашению проекта и передаёт следующей job digest, возвращённый той же сборкой. Registry tag технически можно перепривязать. Deployment использует content-addressed digest как источник истины, поэтому целевой образ нельзя незаметно заменить без изменения ссылки.

Итоговый GHCR package должен быть публичным. Его одноразовый bootstrap выполняется вручную:

  1. Создайте GitHub Environment production, разрешите deployment только из main и не включайте обязательный approval для штатного deployment.

  2. Добавьте четыре Environment secrets без публикации их значений:

    • PRODUCTION_SSH_HOST
    • PRODUCTION_SSH_USER
    • PRODUCTION_SSH_KEY
    • PRODUCTION_SSH_KNOWN_HOSTS
  3. Добавьте Environment variable с выключенным автоматическим deployment:

    PRODUCTION_AUTO_DEPLOY_ENABLED=false
    
  4. Выполните успешный push в main, чтобы workflow впервые опубликовал GHCR image.

  5. В настройках созданного GHCR package вручную измените visibility на public.

  6. Проверьте возможность анонимно скачать ранее опубликованный image по digest.

  7. Убедитесь, что production runtime, ограниченная server-side gateway, healthcheck и smoke-check готовы.

  8. Включите автоматический deployment:

    PRODUCTION_AUTO_DEPLOY_ENABLED=true
    
  9. Убедитесь, что следующий успешный push в main автоматически разворачивает опубликованный образ.

PRODUCTION_AUTO_DEPLOY_ENABLED является Environment variable, а не secret. Workflow не меняет visibility package. Production host скачивает публичный образ анонимно и не хранит GitHub PAT только ради этого pull.

При push image публикуется независимо от значения gate. Если переменная отсутствует или не равна true, deployment получает решение disabled, не готовит SSH material и успешно завершается с понятным summary. Ручной deploy и rollback для явно указанного digest этот gate не блокирует.

После получения concurrency lock автоматический deployment с включённым gate сверяет commit workflow с текущим HEAD main через GitHub API. Устаревший workflow получает решение superseded и не подключается к production host. Ошибка API или некорректный SHA блокируют deployment.

Все четыре транспортных значения считаются secrets. Ключи LLM и CAPTCHA, cookie secret и остальная конфигурация приложения остаются только на production host и не передаются в GitHub Actions.

Обычный deployment не выполняет git pull, не собирает исходники на production host и не требует там рабочей копии репозитория или Node.js. Ограниченная server-side gateway принимает только действие deploy или rollback и digest образа. Она проверяет разрешённый image, обновляет Compose, выполняет healthcheck и HTTP smoke-check без LLM, а при ошибке запускает rollback. Workflow не дублирует эту серверную логику и завершает job с кодом gateway.

Для повторного deployment или ручного rollback откройте workflow CI, запустите его вручную из main, выберите действие и укажите digest ранее опубликованного образа в формате sha256:<64 lowercase hex>. Ручной запуск не собирает и не публикует новый Docker-образ.

Автоматический summary называет ${{ github.sha }} исходным commit SHA. При ручном запуске то же значение обозначено только как workflow ref SHA и не считается commit выбранного образа. Источником истины для ручного действия остаётся digest. Summary также показывает решение, действие и итоговый exit status. Пока server-side протокол не возвращает отдельный стабильный машинно-читаемый статус rollback, workflow не определяет его по тексту stdout и сообщает только об успехе или ошибке gateway.

Текущий production-сценарий использует один контейнер. Короткое окно недоступности во время его замены для MVP считается допустимым.

Локальный запуск

Основная команда локального запуска требует Docker:

make compose

Приложение будет доступно по адресу http://localhost:3000. Необходим Docker Compose 2.24.0 или новее: корневой .env подключается как необязательный env-файл. Поэтому контейнер запускается и в режиме заглушки, а при наличии .env получает заданные LLM_* переменные. Файл .env исключён из контекста сборки и репозитория.

Для разработки без контейнера нужны Node.js 26.3.0 и pnpm 11.12.0:

pnpm install --frozen-lockfile
pnpm dev

Ручной smoke-check последовательно выполняет один платный LLM-запрос для каждого актуального сценария из общего набора fixtures. Фактическое число запросов определяется текущим corpus:

pnpm smoke:llm

Перед запуском настройте провайдера в локальном .env. Не передавайте ключи в командной строке и не сохраняйте вывод smoke-check в репозитории. Команда проверяет конфигурацию до первого запроса, запускается только вручную и не входит в CI или pnpm check.

Автоматическая часть проверяет:

  • соответствие outcome сценарию
  • публичный контракт результата
  • ожидаемое наличие warnings и отсутствие их текста в body
  • отдельный раздел Прошу:
  • ровно один backend-owned пункт требований
  • сохранение всего явного desiredActions без автоматического общего пункта

Для schema-valid результата команда печатает в stdout исходный синтетический ввод, нормализованный публичный результат, mustPreserveFacts и mustNotInvent до остальных структурных проверок. Поэтому отчёт остаётся доступным, даже если формат Прошу: или наличие warnings оказались неверными. Содержимое результата, не прошедшего публичную Zod-схему, не выводится.

В ручной смысловой проверке нужно убедиться, что:

  • значимые факты сохранены
  • домыслы и неподтверждённые категории людей не добавлены
  • выводимый риск обозначен как возможность и имеет непосредственное основание
  • драматичный каскад рисков не добавлен
  • желаемые действия не смешаны с описанием проблемы
  • технический способ ремонта не придуман

Строка Автоматически прошли считает только сценарии, завершившие все автоматические проверки. Код завершения 0 означает только успешное прохождение автоматических проверок. Он не заменяет ручную смысловую проверку результата. Код 1 означает отсутствующую конфигурацию, недоступность провайдера, ошибку запроса, неожиданный outcome или нарушение проверяемого контракта. Обычная ошибка сценария не останавливает остальные сценарии, а общая недоступность провайдера прекращает дальнейшие платные запросы.

Для воспроизводимого сравнения нескольких явно выбранных моделей есть отдельный локальный benchmark. Он использует тот же набор synthetic fixtures, production prompt, Structured Output schema, transport и renderer. Обычный запуск только показывает request plan и оценочную максимальную стоимость, не обращаясь к provider:

pnpm benchmark:llm -- --config .llm-benchmark.local.json

Платный режим требует --run, интерактивный терминал и точную confirmation phrase после показа полного плана. Benchmark не запускается в CI и не входит в pnpm check. Локальные Markdown-отчёты сохраняются в .tmp/llm-benchmark/ и исключены из Git.

При SIGINT или SIGTERM сервер прекращает работу через graceful shutdown. Повторный сигнал не запускает параллельное закрытие. Если app.close() не завершится за 10 секунд, процесс принудительно остановится с ненулевым кодом.

Browser-level проверки

Vitest и happy-dom проверяют клиентскую логику и DOM-контракты без настоящего browser layout. Отдельный набор Playwright Test запускает production-сборку в Chromium и проверяет реальные размеры, переполнение, пересечения, прокрутку, клавиатурный focus и критический путь пользователя.

Для локального отчёта о покрытии Vitest запустите:

pnpm test:coverage

Команда выводит summary в терминал, создаёт HTML-отчёт в coverage/index.html и машиночитаемый LCOV-файл в coverage/lcov.info. Обычный pnpm test coverage не собирает.

Полный browser-level набор запускается одной командой:

make test-browser

Команда собирает приложение и запускает его отдельно от Chromium во внутренней сети Docker Compose. В одном checkout параллельный запуск блокируется через flock, а разные физические checkout получают разные стабильные Compose project names. Локальный Chromium и Playwright browser cache не нужны. Стенд не получает production secrets, не вызывает реальный LLM, SmartCaptcha, Метрику и другие внешние сервисы. Ответы POST /api/generate подменяются через Playwright routing, включая управляемое тестом состояние loading.

Основные layout-инварианты проверяются на каждом viewport:

  • 320 × 568
  • 360 × 800
  • 390 × 844
  • 412 × 915
  • 844 × 390
  • 1280 × 800

После запуска HTML report находится в playwright-report, а screenshots и traces — в test-results. При падении CI job browser эти каталоги сохраняются в artifact playwright-browser-<commit SHA> на 7 дней. Там же остаются обзорные screenshots успешного состояния для viewport 390 × 844 и 1280 × 800. Они не являются screenshot baseline: при изменении разметки их просматривают вручную для оценки визуальной иерархии, которую не описывают геометрические assertions. Обычный pnpm check типизирует browser-тесты, но не запускает Docker и не скачивает браузеры.

Логи

Для каждого POST /api/generate приложение выполняет попытку записи в stdout события generation_started и ровно одного итогового события в виде отдельных JSON-строк. Один requestId связывает весь путь запроса, возвращается клиенту в заголовке x-request-id и входит в безопасный API error response.

События содержат только технические поля и не включают пользовательский ввод, готовую заявку, prompt, response body, ключи, токены, cookies, полный набор заголовков или переменные окружения. Полный контракт событий и классификация статусов описаны в разделе «Структурированное логирование генерации». Приложение не создаёт файлы логов внутри контейнера.

compose.production.yaml направляет stdout/stderr приложения через Docker syslog в отдельный локальный приёмник логов проекта. Дополнительный кеш Docker отключён. rsyslog только записывает в файл проекта. Отдельный таймер раз в минуту запускает logrotate, который проверяет размер maxsize 10M и ежедневное календарное удаление. Порог размера проверяется периодически, а не в момент каждой записи. Схема установки и проверки описана в руководстве по эксплуатации.

docker compose logs и docker logs для production-контейнера с отключённым кешем недоступны. Технические события просматривает только уполномоченный оператор в выделенном файле. Локальный compose.yaml по-прежнему использует json-file с ограничением объёма.

Датированный статус доступа, календарного срока и уничтожения находится в контракте обработки данных.

Команды

  • pnpm dev — запустить приложение для разработки
  • pnpm build — собрать production-версию
  • pnpm start — запустить собранное приложение
  • pnpm smoke:llm — вручную проверить все fixtures реальными LLM-запросами
  • pnpm benchmark:llm — показать план локального сравнения выбранных LLM-моделей
  • make test-production-runtime — проверить production Compose в Docker
  • make test-browser — проверить интерфейс в Chromium через Docker Compose
  • pnpm lint — проверить код с Biome
  • pnpm lint:md — проверить Markdown и окончания файлов
  • pnpm format — отформатировать поддерживаемые файлы
  • pnpm format:check — проверить форматирование
  • pnpm typecheck — проверить типы TypeScript
  • pnpm test — запустить unit-тесты
  • pnpm check — выполнить полный набор проверок

Команды также доступны через одноимённые цели make.

Документация

Лицензия

Проект распространяется на условиях GNU Affero General Public License v3.0 (AGPL-3.0-only). Полный текст лицензии находится в LICENSE.

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages