Skip to content

Latest commit

 

History

124 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TechRole Index

CI Secret scan Public preview

Открыть сайт · Исследования · Daily dataset · Как цитировать · RFC 9264 Linkset

Полный контекст текущего состояния, история работ и план продолжения для нового чата находятся в HANDOFF.md. Описание публикуемого набора, ограничений и правил атрибуции вынесено в DATASET_CARD.md.

Рабочий MVP русскоязычного сервиса аналитики рынка IT-профессий. Текущий публичный release — 0.1.0. Он показывает спрос, зарплаты, Junior/Middle/Senior, выборку, confidence, тренды, типичный стек и объяснимый индекс 0–100. Главный зарплатный график и карточки используют всю сопоставимую зарплатную выборку собственных совпадений профессии из официального HH API: валюты приводятся к RUB, net — к gross, односторонняя граница — к оценке центра, затем зарплаты делятся на три непрерывных сегмента в пропорциях собственных требований роли к опыту. Бесплатный API и SSR получают не более 30 дней истории; Premium может запросить до 180 накопленных календарных дней. «Работа России», подготовленная 180-дневная витрина и открытые исследования остаются отдельными слоями.

Приложение TechRole Index №26065 одобрено HeadHunter для заявленного сценария публичной коммерческой агрегированной аналитики. Серверный токен хранится только в окружении; публично выводятся агрегаты, а не копия вакансий. Условия и технические ограничения зафиксированы в DATA_SOURCES.md.

Архитектура

  • Next.js 16 + TypeScript + App Router + Tailwind + Apache ECharts: SSR публичных страниц, адаптивный UI, light/dark.
  • FastAPI + Pydantic + SQLAlchemy + Alembic: REST API, OpenAPI, auth и серверный paywall.
  • PostgreSQL: основная БД, подготовленные daily-метрики и изолированная observed_publication_metrics_daily с partition upsert/quality gate. AnalyticsSink подготовлен для будущего ClickHouse.
  • Redis + Celery worker/beat: очередь обращений, fail-open cache каталога/detail с коротким TTL и прикладные фоновые задачи.
  • Dagster webserver + daemon: nightly ingestion в 00:00 Europe/Moscow, история запусков, email-отчёт и зависимый materialization op только после успешного ingestion.
  • Официальный XML Банка России: отключаемый real currency provider и replayable snapshots requested/effective date для USD/EUR/KZT.
  • Caddy: production reverse proxy и security headers.
  • Контейнеры local-dev: frontend, backend, worker, scheduler, dagster-webserver, dagster-daemon, postgres, redis; одноразовые migrate и seed.
  • CI: Ruff, mypy, pytest, ESLint, TypeScript, Vitest, production build, Compose, rendered production security contract, Gitleaks по полной истории и отдельный Chromium E2E job; Dependabot подготовлен для трёх экосистем.
  • Prometheus-compatible observability: внутренний /metrics, bounded route-template labels, multiprocess Gunicorn storage и отдельный loopback-only профиль с alert rules.
  • Windows public preview: production standalone на 3100 стоит за постоянным loopback Node proxy на 3199, поэтому SSH сохраняет локальный listener при переключении immutable build slot; отдельный refresh отслеживает возможную ротацию временного hostname самим провайдером.

Подробности: ARCHITECTURE.md, формулы: METHODOLOGY.md, решения: DECISIONS.md, платежи и действия владельца: PAYMENTS.md, варианты размещения: HOSTING.md, белая стратегия запуска: GROWTH.md, вклад в проект: CONTRIBUTING.md, приватное сообщение об уязвимости: SECURITY.md.

Быстрый запуск

Требуются Docker Desktop с Compose v2 и свободные loopback-порты 3000/3001/8000.

Copy-Item .env.example .env
# Обязательно замените APP_SECRET_KEY и demo-пароли в .env.
docker compose up --build

После успешного запуска:

Карточка профессии выстроена от ответа к методическим оговоркам: типичный стек → фактические доходы → выразительные графики зарплаты/публикаций/полноты → неизменённый расчётный ряд вакансий → период → provenance-слои → вклад факторов индекса и навыки/регионы. У каждого цитируемого блока есть стабильный fragment, а JSON-LD зарплатного Dataset совпадает с видимыми значениями и ссылается на первичные источники.

Для публичного GitHub также подготовлен корневой CITATION.cff; канонический URL берите с /citation, потому что временные preview-host меняются.

Daily JSON/CSV/Schema публикуют SHA-256 ETag; JSON и CSV также отдают Last-Modified. Клиенты могут использовать If-None-Match или If-Modified-Since и получать пустой 304 Not Modified, когда материализация не изменилась.

PostgreSQL и Redis не публикуют порты на host. Backend и frontend доступны только на loopback в local-dev.

Локальный Prometheus UI поднимается отдельно на http://127.0.0.1:9090:

docker compose -f compose.yaml -f compose.observability.yaml --profile observability up -d prometheus

Метрики не проходят через публичный frontend/Caddy. Alert rules проверяют недоступность backend, долю 5xx и p95 latency; внешняя доставка требует отдельно настроенного Alertmanager.

Форма личного ведения отправляется внутри сайта через отдельный endpoint /api/v1/mentorship/requests, сохраняет заявку с номером и передаёт её собственной Celery-задаче. Отдельное поле принимает предлагаемую стоимость в рублях и передаёт её только как часть обращения: оно не создаёт платёж и не определяет сумму будущего заказа. Формат, объём работы и итоговые условия согласуются индивидуально. Письма личного ведения имеют префикс [TechRole Mentorship], а обращения поддержки - [TechRole Support].

Обе формы сохраняются в PostgreSQL и используют общую заменяемую SMTP-инфраструктуру, но разные таблицы, API, задачи и шаблоны писем. По умолчанию SMTP выключен, чтобы проект не содержал почтовых секретов. Для реальной доставки на sqldevelopermoscow@yandex.com добавьте пароль приложения почтового ящика только в локальный .env и включите доставку:

SUPPORT_EMAIL_ENABLED=true
SUPPORT_RECIPIENT_EMAIL=sqldevelopermoscow@yandex.com
SMTP_HOST=smtp.yandex.ru
SMTP_PORT=465
SMTP_USERNAME=sqldevelopermoscow@yandex.com
SMTP_PASSWORD=<пароль-приложения>
SMTP_FROM_EMAIL=sqldevelopermoscow@yandex.com
SMTP_USE_SSL=true

В настройках Яндекс Почты также должен быть включён доступ почтовых программ: «С сервера imap.yandex.ru по протоколу IMAP» и «Пароли приложений и OAuth-токены». После изменения перезапустите backend, worker, scheduler и Dagster: docker compose up -d --build backend worker scheduler dagster-webserver dagster-daemon. Обычный пароль от почты использовать не следует; SMTP_PASSWORD никогда не коммитится.

На локальной машине владельца 2026-07-19 доставка включена и проверена end-to-end: IMAP подтвердил пять писем с отдельными префиксами [TechRole Support], [TechRole Mentorship] и [TechRole Nightly]. Это состояние локального .env; секреты в репозиторий не добавлены.

Миграции и demo-данные

Обычный docker compose up сначала запускает alembic upgrade head, затем идемпотентный seed.

docker compose run --rm migrate alembic upgrade head
docker compose run --rm migrate alembic current
docker compose run --rm seed python -m app.seed
docker compose exec -T backend python -m app.materialize_publication_metrics --source trudvsem_open
# Полное пересоздание только demo-БД:
docker compose run --rm seed python -m app.seed --force

Seed фиксирован DEMO_SEED=20260717, заканчивается snapshot-датой 2026-07-17 и включает рост, падение, нейтральные роли, пропуски зарплаты, RUB/USD/EUR/KZT и редкие профессии с insufficient.

Demo-пользователи

Создаются только при DEMO_MODE=true. Email:

  • free@example.com;
  • premium@example.com;
  • admin@example.com.

Пароли берутся из DEMO_FREE_PASSWORD, DEMO_PREMIUM_PASSWORD, DEMO_ADMIN_PASSWORD в локальном .env. Значения из .env.example предназначены только для первого локального запуска и должны быть заменены. Хеши Argon2 сохраняются в БД, открытые пароли — нет.

Команды разработки

Backend без Docker:

python -m venv .venv
.\.venv\Scripts\python -m pip install -e "backend[dev]"
Set-Location backend
..\.venv\Scripts\alembic upgrade head
..\.venv\Scripts\python -m app.seed
..\.venv\Scripts\uvicorn app.main:app --reload

Frontend:

Set-Location frontend
corepack enable
pnpm install --frozen-lockfile
pnpm dev

Worker/scheduler:

docker compose up worker scheduler

Nightly ingestion и наблюдение:

docker compose up -d dagster-webserver dagster-daemon
docker compose exec -T dagster-webserver dagster schedule list -w workspace.yaml
# UI истории запусков и ручного старта:
Start-Process http://localhost:3001

Безопасная повторная классификация уже загруженных официальных публикаций:

docker compose exec -T backend python -m app.reclassify_open_data --dry-run
docker compose exec -T backend python -m app.reclassify_open_data --apply

Команда не перезаписывает AI- или ручные решения. Текущий rules-v3 объединяет слишком узкие технологические дубли с их устойчивыми профессиями и добавляет массовые роли с консервативными исключениями. Каноническая title-классификация остаётся консервативной, а полнота поиска HH хранится отдельно: точная фраза ищется только в названии вакансии, результаты алиасов дедуплицируются внутри профессии и могут образовывать many-to-many связь. Production-снимок от 2026-08-06 содержит 20 389 уникальных вакансий текущей выдачи и 24 159 связей с 50 профессиями; минимум отдельной роли — 8. Актуальные production-счётчики фиксируются в HANDOFF.md после каждого полного прохода.

Расписание techrole_midnight_moscow использует cron 0 0 * * * и timezone Europe/Moscow, поэтому не зависит от UTC внутри контейнера. При включённом NIGHTLY_REPORT_EMAIL_ENABLED=true результат имеет отдельную тему [TechRole Nightly] ...; неуспех почты фиксируется в результате, но не скрывает итог ingestion.

Тесты и статические проверки:

Set-Location backend
..\.venv\Scripts\ruff check .
..\.venv\Scripts\mypy app
..\.venv\Scripts\pytest

Set-Location ..\frontend
pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm prepare:standalone
pnpm test:e2e
pnpm audit:public

Set-Location ..
docker compose config --quiet
docker compose --profile local-dev run --rm dev-tests
# Изолированный Chromium runner, не запускается в обычном окружении:
docker compose --profile e2e run --rm e2e
# Проверка всех canonical HTML URL из sitemap внутри запущенного frontend:
docker compose exec -T frontend npm run audit:public

Тестовый контур платежей

DemoPaymentProvider проводит полный sandbox-сценарий без списания: сервер формирует продукт и цену, сохраняет заказ и версию принятых условий, проверяет HMAC webhook/idempotency и только после succeeded выдаёт отдельный entitlement на 30 дней. Отмена терминальна; полный административный возврат идемпотентен и отзывает только доступ этого заказа.

После подтверждения владельцем статуса самозанятого НПД основной вариант — Robokassa с «Робочеками СМЗ», резервный — ЮKassa. Оба адаптера определяют товар, сумму и чек только из расширяемого server-side каталога. Robokassa передаёт подписанную номенклатуру HTML-формой POST на канонический Merchant/Index.aspx, проверяет ResultURL, дедуплицирует повторы и сверяет live-возвраты. Официальный sandbox, «Робочеки СМЗ» и автоматический live-readiness подтверждены. Контрольный live-платёж 290 ₽ от 24 июля 2026 года получил расчётное состояние Robokassa, ResultURL, фискальный чек в Robokassa/«Мой налог» и Premium ровно на 30 дней; production работает в robokassa/live. Test/live Password №1/№2 разделены, Password №3 live-only, а каждый заказ хранит неизменяемый snapshot услуги. Инструкция: PAYMENTS.md.

Администратор видит безопасный checklist на /admin: отдельно для тестового магазина и реальных платежей. GET /api/v1/admin/payment-readiness возвращает только булевы признаки и готовый ResultURL, но никогда не возвращает MerchantLogin, пароли или API-ключи. Реальный режим дополнительно требует PAYMENTS_STABLE_HTTPS_CONFIRMED=true; Tailscale Funnel этим условием не считается.

На той же странице admin может найти пользователя по email, имени или ID и включить/снять бессрочную ручную выдачу Premium. Переключение в Free отзывает только entitlement с административным источником; оплаченный доступ сохраняется и снимается только штатным refund-процессом. Список не содержит password hash, платёжных идентификаторов или credentials, mutation защищена RBAC + CSRF и записывается в audit_logs.

Privacy-first аудитория доступна владельцу на /admin/analytics: consented visitors, pageviews, clicks, citation signals и declared crawler requests без IP/email/raw User-Agent. Admin и automation исключаются. Ограничения и production enablement: ANALYTICS.md. Аудит поискового и AI-слоя и ручные Search Console/Яндекс шаги: SEARCH_AI.md.

Подключение законного источника

  1. Получить и документировать право использования данных.
  2. Реализовать/настроить VacancyDataProvider, не меняя расчётный слой.
  3. Для одобренного приложения официального HH API установить серверный application token и все guards:
HH_ENABLED=true
HH_COMMERCIAL_USE_CONFIRMED=true
HH_CONTACT_EMAIL=owner@example.com
HH_APP_NAME=TechRoleIndex
HH_ACCESS_TOKEN=server-only-token
# Recommended: lets the backend renew an expired application token server-side.
HH_CLIENT_ID=server-only-client-id
HH_CLIENT_SECRET=server-only-client-secret
HH_HISTORY_DAYS=365
HH_MAX_PROFESSIONS=50
HH_MAX_PAGES_PER_QUERY=20
HH_USE_ALIAS_QUERIES=true
HH_DETAIL_REQUEST_DELAY_SECONDS=0.25
HH_DETAIL_MAX_RECORDS_PER_RUN=10000
HH_DETAIL_BATCH_SIZE=50

Provider использует официальный API, HH-User-Agent, страницы до 100 элементов и документированный предел глубины 2 000 результатов на один поисковый запрос. Запросы по имени и алиасам дедуплицируются по vacancy id; сохраняется только безопасный allowlist полей. Для уверенно классифицированных вакансий возобновляемый detail-проход получает навыки, языки, опыт, занятость, рабочие форматы, графики и публичные id/названия работодателей. Описания, контакты, адреса, ссылки отклика и персональные данные не сохраняются. На сайте работодатели публикуются только как топ-5 по профессии и единая группа «Другие компании». Источник изолирован от «Работы России», gross/net не смешиваются. HTML scraping, обход rate limit, CAPTCHA и прокси-ротация не используются. Условия: DATA_SOURCES.md.

Ручной полный или ограниченный detail-проход:

docker compose exec -T backend python -m app.enrich_hh_vacancies --max-records 10000 --batch-size 50

Команда идемпотентна, пропускает уже обработанные карточки и фиксирует прогресс пакетами. Ночной Dagster pipeline запускает её после успешного HH-поиска, поэтому в обычном режиме дозагружаются только новые вакансии.

Для чернового законного контура реализован TrudvsemOpenDataProvider, который читает только официальный JSON API портала «Работа России»:

TRUDVSEM_ENABLED=true
TRUDVSEM_QUERY_LIMIT=100
TRUDVSEM_MAX_PROFESSIONS=50
TRUDVSEM_HISTORY_DAYS=180
TRUDVSEM_MAX_PAGES_PER_QUERY=100
TRUDVSEM_USE_ALIAS_QUERIES=true
TRUDVSEM_REQUEST_DELAY_SECONDS=0.25

Provider сохраняет только allowlist профессиональных полей и не сохраняет контакты/адреса. Налоговый статус зарплаты считается неизвестным; поэтому ingestion хранит наблюдения и provenance, но не перезаписывает публичную gross-витрину. Публичный 180-дневный ряд показывает публикации по creation-date, а не историю одновременно активных вакансий. HTML-скрейпинг не используется.

Основной публичный зарплатный слой использует все сопоставимые HH-записи собственной профессии. RUB/USD/EUR/KZT приводятся в RUB по сохранённому курсу ЦБ, net пересчитывается в gross по шкале НДФЛ, полная вилка представляется midpoint, а одна граница — центром, оценённым через типичную ширину полных вилок роли. В каждом 30-дневном окне зарплаты сортируются и делятся на Junior/Middle/Senior в пропорциях требований роли к опыту со слабым prior 15/45/40; сумма n сегментов равна полной использованной выборке. Направление и общий IT-рынок в профессию не подставляются. Бесплатная выдача физически обрезает историю до 30 дней, Premium — до 180 накопленных дней; 90/180 не являются прогнозом. «Работа России» продолжает публиковать свои точные счётчики и provenance отдельно.

Публичные ориентиры фактических доходов

backend/app/data/salary_benchmarks.py хранит версионированные факты из публичных отчётов отдельно от vacancy observations. Главный источник — Хабр Карьера, I полугодие 2026, n=45 226: ролевые P10/median/P90, медианы технологий и значения Москва/Санкт-Петербург/другие регионы. Для грейдов используются отдельно помеченные публикации Хабр Карьеры о Junior, Grades/GetGrade 2025 и опрос Профсоюза работников ИТ за I полугодие 2026 с 1 539 российскими ответами. Последний даёт только общерыночные медианы Junior 114 500 ₽, Middle 200 000 ₽ и Senior 310 000 ₽; gross/net не указан, поэтому он используется только для отсутствующих ролевых грейдов и всегда подписывается как общий IT-рынок. Дополнительно сохранён датированный снимок восьми точных и двух смежных медиан, которые официальный калькулятор Хабр Карьеры публично сообщает в SEO-описании отфильтрованных страниц; для NLP явно используется более широкий ML-срез, для SAP — ERP-срез. Скрытые входом значения не извлекались, а n и gross/net остаются неизвестными.

Порядок интерпретации строгий: точная профессия → технологический или смежный срез → значение направления. P10/P90 не выдаются за Junior/Senior, категорийная медиана не становится ролевой, а исследование фактических доходов не записывается в таблицы вилок вакансий. После замены десяти профессий слой открытых исследований содержит 29 прямых, 10 смежных и 11 категорийных ориентиров; основная зарплатная модель HH от этого резервного слоя не зависит.

Опциональный Dagster op verify_public_salary_benchmarks проверяет только публичные title и meta description, никогда не входит в аккаунт и не меняет снимок автоматически. Подтверждённое изменение метаданных останавливает op; временная недоступность источника отмечается отдельно и не выдаётся за изменение числа. Включение: SALARY_SOURCE_AUDIT_ENABLED=true. Ручная проверка:

Каждый включённый запуск сохраняет итог в существующий PostgreSQL audit_logs. Публичный /status показывает время последней проверки и счётчики verified/changed/unavailable, но не публикует внутренние логи и не принимает результат проверки от браузера.

Отдельная страница /salary-benchmarks собирает этот справочный слой по всем 50 профессиям и даёт переносимые /salary-benchmarks.json и /salary-benchmarks.csv. JSON дополнительно сообщает покрытие грейдов 50 профессий / 150 точек. В выгрузках остаются coverage (direct/related/category), география, метрика, период, n, tax status, первичный URL и методология. Это не открытая лицензия на материалы источников: дальнейшее использование сверяется с условиями каждого первичного источника.

docker compose exec -T backend python -m app.services.salary_source_audit

Ручной запуск официального слоя:

docker compose exec -T backend python -c "from app.database import SessionLocal; from app.services.open_data_ingestion import ingest_trudvsem_open_data; db=SessionLocal(); print(ingest_trudvsem_open_data(db).to_dict()); db.close()"

Optional Ollama

Основной классификатор rule-based и не требует GPU. Ollama включается только для неопределённых записей:

AI_CLASSIFIER_ENABLED=true
OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_MODEL=<локальная-модель>

Запуск профильного helper: docker compose --profile ai up. Для интерактивной работы выбирайте модель, которая помещается в доступные ~8 ГБ VRAM; более крупная модель использует медленный CPU/RAM fallback. Ответ AI получает потолок confidence 0.79 и не заменяет ручную проверку.

На проектной Windows-машине 2026-07-18 установлена qwen3.6:27b Q4_K_M (17 ГБ). В 4K-проверке она дала правильный структурированный ответ, но работала с распределением примерно 70% CPU / 30% GPU и почти полностью заняла 32 ГБ RAM и 8 ГБ VRAM. Это максимальная по качеству локальная модель для редких или ночных задач, а не для быстрого интерактива: холодный короткий запрос занял 79 секунд, генерация - около 1,46 токена/с. Docker-контейнер успешно получил от неё валидную классификацию nlp-engineer. В локальной конфигурации AI разрешён только для максимум трёх неопределённых вакансий за nightly-run, confidence ограничен 0.79, slug проверяется по каталогу, а после запуска модель выгружается из памяти.

Воспроизводимый доменный benchmark использует 20 синтетических русскоязычных кейсов без реальных вакансий и персональных данных. Он проверяет строгий JSON Schema, близкие IT-роли, seniority и обязательное воздержание на трёх нецелевых вакансиях. Запуск из PowerShell:

$workspace = (Get-Location).Path
docker compose run --rm --no-deps -v "$workspace\outputs:/outputs" backend python -m app.ai_benchmark --model qwen3.6:27b --output /outputs/ai-classifier-report.json

Контрольный прогон 2026-07-21 завершился за 276,4 секунды: schema/slug/seniority/exact — 20/20, корректные отказы — 3/3. Первый холодный кейс занял 40,2 с, последующие — 10–14 с; после прогона ollama ps подтвердил выгрузку модели. Это ограниченный synthetic quality gate, а не доказательство качества на будущих реальных распределениях: rule-based классификатор и ручная проверка остаются главными.

Официальные сведения: Ollama qwen3.6:27b, Qwen3.6-27B model card и benchmarks, Ollama v0.32.1.

Production

docker compose -f compose.yaml -f compose.production.yaml --profile production up --build -d

Публичные механики распространения: динамическая OG-картинка и кнопки «Поделиться»/«Скопировать цитату» на каждой профессии, сохраняемые ссылки /compare?slugs=..., еженедельный отчёт /reports/weekly и RSS /feed.xml. После успешного nightly materialization Dagster отправляет sitemap в IndexNow, когда INDEXNOW_ENABLED=true и задан INDEXNOW_KEY. Telegram-дайджест запускается по понедельникам в 09:00 МСК только при TELEGRAM_DIGEST_ENABLED=true, TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID.

Реквизиты юридических страниц читаются только из server-side переменных SELLER_NAME, SELLER_INN, SELLER_PHONE, SELLER_ADDRESS, SELLER_EMAIL и LEGAL_EFFECTIVE_DATE; их реальные значения не коммитятся.

Перед запуском установите сильный APP_SECRET_KEY, DEMO_MODE=false, один HTTPS origin в SITE_ADDRESS/PUBLIC_BASE_URL/FRONTEND_ORIGIN/NEXT_PUBLIC_SITE_URL и отдельные согласованные POSTGRES_*/DATABASE_URL credentials. Используйте пустую production-БД: local demo volume намеренно отвергается. Production override убирает все host-порты кроме Caddy 80/443, исключает source bind-mounts и передаёт canonical URL как Docker build arg. Rendered config проверьте через infra/validate-production-config.mjs. См. DEPLOYMENT.md и SECURITY.md.

Типичные проблемы Windows / Docker Desktop

  • docker не найден: запустите Docker Desktop, включите WSL2 backend, перезапустите терминал и проверьте docker compose version.
  • Bind mount медленный: храните репозиторий в WSL2 filesystem либо исключите каталог node_modules из антивирусного real-time scan согласно политике организации.
  • pnpm долго перестраивает ссылки: используйте Node 22/Corepack и pnpm install --frozen-lockfile; не смешивайте npm и pnpm в одном node_modules.
  • Порты заняты: Get-NetTCPConnection -LocalPort 3000,8000; остановите конфликтующий local service.
  • Seed кажется зависшим: он вставляет 108 000 daily-срезов; дождитесь строки Seeded 50 professions и проверьте docker compose logs seed.
  • Docker не видит Ollama: разрешите localhost listener Ollama и используйте host.docker.internal:11434; AI всё равно можно оставить выключенным.

About

Evidence-backed index of data engineering roles, skills, trends, and machine-readable open datasets.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages