WP Manifestindependent plugin directory
manifest / content / llm-agent-github-repos

GitHub Repo Collector

WordPress-плагин: сборщик подборок GitHub-репозиториев с карточками, README (GitHub-HTML) и переводом Yandex.

by llm-agent.ru · github.com/geliorn/llm-agent-github-repos

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/geliorn/llm-agent-github-repos/archive/refs/heads/main.zip

GitHub Repo Collector (llm-agent-github-repos)

Версия 0.3.0 · Самодостаточный WordPress-плагин: собирает подборки GitHub-репозиториев по настраиваемым правилам (топики, звёзды, свежесть) и публикует их на сайте карточками. Не зависит от ACF/CPT UI и вообще от каких-либо других плагинов — переносим на любой сайт копированием папки.

Проектный план и обоснование решений — docs/github-repo-parser-plan.md в корне репозитория. Этот файл — справочник по тому, что реально реализовано в коде.


1. Структура данных

Типы записей (регистрируются в коде, class-lagr-cpt.php)

  • github_repo — собранные репозитории. Публичный, с архивом, show_in_rest. Поддержка: title, editor (используется под README/LLM-текст через post_content), thumbnail, revisions.
  • github_repo_rule — правила сбора. Непубличный (только в админке), без REST.

Таксономии

  • repo_topic — своя таксономия. Всегда ровно один термин на запись — тот топик, что задан в правиле сбора (не путать с полным набором родных топиков репозитория на GitHub).
  • repo_language — своя, один термин: основной язык репозитория (language из Repos API).
  • repo_license — своя, один термин: SPDX id лицензии.
  • Остальные топики репозитория (всё, что в GitHub-топиках репозитория, но не совпало с топиком правила) + теги от LLM (если включена генерация) — уходят в настраиваемую таксономию, по умолчанию post_tag (общий словарь с блогом). См. настройку «Топики репозитория» → «Куда девать остальные топики». Значения: post_tag (по умолчанию), repo_topic (слить всё в одну, старое поведение), none (не добавлять), либо любая другая зарегистрированная публичная таксономия сайта. Связь выбранной таксономии с github_repo регистрируется автоматически на init (LAGR_CPT::link_extra_topics_taxonomy()), не требует ручной настройки темы или разового скрипта.

Важно про /tag/x/ и /category/x/ (встроенные таксономии). Одной привязки таксономии недостаточно, чтобы репозитории реально показывались на архиве тега — WordPress по умолчанию ограничивает запрос архива встроенной таксономии post_type=post, даже когда к ней подключены другие типы записей (в отличие от is_tax()-архивов вроде нашего repo_topic, где WP сам расширяет post_type). Без дополнительного кода /tag/ai/ показывал бы 0 репозиториев, хотя термины и счётчики были бы верными. Исправлено хуком pre_get_posts — LAGR_CPT::include_shared_taxonomies_in_archives() — расширяет post_type на архиве встроенной таксономии до всех реально зарегистрированных для неё типов.

Также нужна разовая настройка Relevanssi, если используется как поисковый движок (см. integration_relevanssi): по умолчанию он индексирует только post/page — github_repo нужно добавить в опцию relevanssi_index_post_types и пересобрать индекс (админка: Relevanssi → Индексация → «Построить индекс», либо разово relevanssi_build_index()). Без этого поиск по сайту не находит ни одного репозитория и не показывает при этом никакой ошибки — просто пустая выдача.

Поля (register_post_meta, show_in_rest: true — поэтому Block Bindings в шаблонах работают сразу, без дополнительного mu-plugin)

Полный список — LAGR_Meta_Fields::repo_meta_keys():

Meta-ключ Источник
_gh_full_name Repos/Search API, ключ дедупа
_gh_stars, _gh_forks, _gh_watchers, _gh_open_issues Repos API
_gh_language, _gh_license, _gh_homepage Repos API
_gh_created_at, _gh_pushed_at, _gh_updated_at Repos API
_gh_default_branch, _gh_size_kb, _gh_owner_login, _gh_owner_type, _gh_owner_avatar Repos API
short_description description репозитория (обрезано до ~200) либо первая строка README, либо LLM
_gh_score stars + forks × score_forks_weight (настройка)
_gh_rule_id ID правила, которое собрало запись
_gh_needs_review 1, если данные источника изменились после первого сбора — не перезаписываем молча
_gh_source_description «снимок» description на момент сбора — с чем сравнивать при повторном проходе
_gh_generated_by_llm 1, если short/full description сгенерированы ИИ, а не взяты как есть
_gh_contributors_count, _gh_latest_release_tag, _gh_latest_release_at, _gh_languages_json, _gh_top_files_json опциональные поля — см. раздел 4

Полное описание (README, сконвертированный из Markdown, либо текст от LLM) пишется не в отдельное поле, а в нативный post_content — так работает стандартный блок core/post-content и автоматически подключается Contextual Related Posts (у него хук на the_content).


2. Правила сбора (github_repo_rule)

Все поля из LAGR_Meta_Fields::rule_meta_keys(), редактируются на экране правила (class-lagr-meta-fields.php::render_rule_box):

Поле Meta-ключ Дефолт
GitHub-топики (через запятую, комбинируются через И) _lagr_topics —
Звёзды: оператор _lagr_stars_operator >
Звёзды: число _lagr_stars_value —
Свежесть, дней (0 = без ограничения) _lagr_freshness_days —
Исключать форки / архивные / требовать описание _lagr_exclude_forks, _lagr_exclude_archived, _lagr_require_description вкл
Мин. размер, КБ _lagr_min_size_kb —
Сортировка (updated/stars/forks) _lagr_sort_by updated
Лимит за прогон (макс. 100 — предел Search API) _lagr_limit —
Сырой GitHub-запрос (продвинутый режим) _lagr_raw_query пусто
Куда публиковать (post type) _lagr_target_post_type github_repo
Режим публикации _lagr_publish_mode draft
Интервал запуска, минут (минимум 10 — см. раздел 5) _lagr_interval_minutes 1440 (24ч)
Включено _lagr_enabled —

Если _lagr_raw_query заполнено — используется как есть вместо сборки из топиков/звёзд/свежести (LAGR_Query_Builder::build_from_rule()). GitHub-запрос поддерживает и квалификатор размера (size:>=N), добавляется в запрос автоматически.

Валидация при сохранении. При каждом сохранении правила (LAGR_Meta_Fields::save_rule_meta) плагин реально выполняет тестовый запрос к GitHub (LAGR_GitHub_Client::test_search()) и пишет результат («нашлось N репозиториев» либо текст ошибки) в _lagr_last_test_result — показывается прямо на экране редактирования.

Служебные, не редактируются вручную: _lagr_last_run_at, _lagr_running_since, _lagr_last_run_summary, _lagr_last_test_result — пишутся только кодом коллектора/планировщика.

Стартовые правила (class-lagr-rule-defaults.php)

При активации плагина создаётся ~41 правило — весь набор топиков из refs/git-post-bot (build_presets()) и refs/git-digest-bot (digest_topics), плюс agent-skills. Включены по умолчанию только три: «AI-агенты» (ai-agent, >50), «Agent Skills» (agent-skills, >10), «MCP» (mcp, >30). Остальные — данные, не код, можно включать/редактировать/удалять как угодно.


3. Сбор данных (class-lagr-collector.php)

LAGR_Collector::run_rule( $rule_id ):

  1. Проверка лока правила (_lagr_running_since, протухает через 30 минут — см. раздел 5).
  2. Проверка бюджета GitHub API — если исчерпан на текущий час, правило пропускается целиком.
  3. Сборка/использование GitHub-запроса, вызов /search/repositories (sort/order/per_page из настроек правила).
  4. Фильтр require_description (форки/архив/размер/свежесть уже зашиты в сам запрос через квалификаторы fork:/archived:/size:/pushed:).
  5. Дедуп по _gh_full_name (WP_Query по мете) — если уже существует, при изменении звёзд/description ставится _gh_needs_review = 1, поля не трогаются молча (та же non-destructive политика, что и в основном ТЗ каталога).
  6. Для новых кандидатов: README (/repos/{full}/readme), опциональные поля (раздел 4), скоринг, генерация описаний (раздел 6), wp_insert_post (черновик или сразу публикация — по настройке правила), таксономии (раздел 1), SEO meta description (LAGR_SEO::apply_meta_description() — раздел 8а).
  7. Итоговая сводка — в _lagr_last_run_summary и в журнал (LAGR_Log).

Markdown → HTML fallback (когда LLM выключен) — минимальный конвертер без зависимостей (markdown_to_html()): заголовки, **bold**, ссылки, вырезает бейджи/картинки-шапки и типовые raw-HTML обёртки логотипов (<picture>, <div align="center">, <img>). Не идеален для экзотичной разметки — известное ограничение, не проблема для основного текста.


4. Опциональные поля

Выключены по умолчанию (настройки плагина), запрашиваются только для отфильтрованных кандидатов, не для сырых результатов поиска — экономия бюджета GitHub API:

Настройка Метод клиента Meta-поле
Контрибьюторы get_contributors_count() (через заголовок Link, per_page=1) _gh_contributors_count
Релизы get_latest_release() _gh_latest_release_tag, _gh_latest_release_at
Языки (%) get_languages() _gh_languages_json
Верхнеуровневые файлы get_top_level_contents() _gh_top_files_json

4а. Язык и лицензия — отдельные переключатели, не «опциональные поля»

_gh_language/_gh_license — другая история: это бесплатные данные, приходят вместе с базовым ответом Repos API (без доп. запроса) и сохраняются в meta всегда, независимо ни от каких настроек. Отдельными настройками (collect_language, collect_license, обе выключены по умолчанию) управляется только:

  • присвоение таксономии repo_language/repo_license при сборе (class-lagr-collector.php::create_repo_post());
  • показ языка/лицензии в паттернах — карточка (card_markup()) и single-страница (single_repo_layout_markup()), через хелперы LAGR_Patterns::language_term_block()/license_term_block().

Выключены по умолчанию осознанно: основной контент сети — Agent Skills и MCP-серверы, для них язык/лицензия обычно не главный признак каталогизации (в отличие от общего каталога кода). Для другой тематики сайта сети — включить оба чекбокса в настройках, взять данные из уже сохранённого _gh_language/_gh_license не получится задним числом для уже собранных записей (таксономия не была присвоена) — понадобится либо ре-собрать, либо разовый скрипт wp_set_object_terms() по уже сохранённой meta.

Боковой фильтр Filter Everything (раздел 8в) изначально включал фильтры «Язык»/«Лицензия» — убраны из набора вручную 2026-09-04 вслед за выключением этих настроек, чтобы не оставлять на архиве рабочий фильтр по несуществующим в разметке полям.


5. Расписание (class-lagr-scheduler.php, cron.php)

Три независимых способа запустить один и тот же тик — решение принято после разбора refs/wp-automatic (устойчивый cron даже на старом PHP, потому что не зависит от WP-CLI):

  1. WP-Cron, кастомный интервал 10 минут (lagr_10_minutes, регистрируется через cron_schedules). Работает из коробки, зависит от посещаемости сайта.
  2. HTTP-эндпоинт с секретом — ?github_repos_cron=СЕКРЕТ на главный URL сайта (LAGR_Scheduler::maybe_handle_http_trigger(), хук init). Секрет — 32 случайных hex-символа (LAGR_Settings::regenerate_secret()), не придумывается руками (в отличие от wp-automatic, где дефолтный секрет — предсказуемое слово cron). Сравнение через hash_equals(). Свой троттлинг — не чаще раза в минуту даже с верным секретом.
  3. Прямой PHP CLI, cron.php в корне плагина — подключает wp-load.php напрямую (../../../wp-load.php), в обход WP-CLI. При вызове из HTTP (не из CLI) тоже требует секрет.

Все три ведут в LAGR_Scheduler::run_tick() — общая логика: взять включённые правила, сравнить _lagr_interval_minutes (минимум 10 — тик физически не может сработать чаще собственной частоты) с _lagr_last_run_at, запустить созревшие.

Блокировки:

  • Лок правила (_lagr_running_since) — таймаут 30 минут (три тика), не 10, чтобы не словить гонку ровно на границе интервала.
  • Лок самого тика — транзиент lagr_tick_running, TTL 9 минут (с запасом внутри 10-минутного окна).

Бюджет GitHub API (class-lagr-github-client.php) — опция _gh_api_budget ({hour, used}), сбрасывается автоматически при смене часа. Порог — настройка gh_budget_threshold (дефолт 4500 из 5000, запас на ручные операции).

Готовые команды для внешнего крона показываются прямо в настройках плагина (LAGR_Scheduler::get_trigger_commands()) — curl / wget / прямой php cron.php, ничего не нужно вычислять руками.


6. Генерация описаний (class-lagr-llm-client.php)

Выключено по умолчанию (llm_enabled = false) — тогда short_description = обрезанное description репозитория (либо первая строка README), full_description/post_content = README как есть через Markdown-конвертер.

Если включено — один вызов на репозиторий, промпт просит вернуть JSON {short_description, full_description, tags} (см. LAGR_LLM_Client::SYSTEM_PROMPT). Разбор ответа терпим к обёртке в ```json (модели иногда так делают вопреки инструкции).

4 провайдера, все — один HTTP-клиент с разными пресетами (LAGR_Settings::llm_provider_config()), потому что все четыре — OpenAI-совместимый chat/completions:

Провайдер Endpoint Авторизация
Yandex Foundation Models ai.api.cloud.yandex.net/v1/chat/completions Api-Key + x-folder-id
DeepSeek напрямую api.deepseek.com/v1/chat/completions Bearer
Timeweb (TWC AI API) agent.timeweb.cloud/.../agents/{agent_access_id}/v1/chat/completions Bearer + x-proxy-source; поле model игнорируется — модель настроена на стороне агента в панели Timeweb
OpenRouter openrouter.ai/api/v1/chat/completions Bearer

7. Скоринг

score = stars + forks × score_forks_weight   (по умолчанию вес 3)

Используется только для сортировки очереди модерации по важности — не влияет на сам факт публикации (это решает _lagr_publish_mode).


8. Админка (class-lagr-admin.php)

Пункт меню «GitHub-репозитории» (lagr-main) + подпункты, зарегистрированные плагином:

  • Очередь модерации (стартовая страница) — таблица правил (статус, интервал, последний запуск, кнопка «Запустить сейчас»), очередь черновиков по score с кнопкой «Опубликовать», список записей с флагом «требует проверки».
  • Настройки — GitHub-токен, бюджет запросов/час, опциональные поля (раздел 4), язык и лицензия репозитория (раздел 4а), топики репозитория (куда девать «остальные» — раздел 1), интеграции с другими плагинами (раздел 8а), вес скоринга, генерация LLM (раздел 6), секрет и команды cron (раздел 5).
  • Журнал — последние ~200 записей лога (LAGR_Log), человекочитаемый список того, что и когда собрал планировщик.

Плюс штатные экраны CPT: Правила сбора (список/редактирование github_repo_rule), GitHub-репозитории (список/редактирование github_repo).


8а. Интеграции с другими плагинами (class-lagr-seo.php, LAGR_Settings::integration_enabled())

Не каждый из 20 сайтов сети обязательно будет держать одинаковый набор сторонних плагинов — секция настроек «Интеграции» даёт переключить это без правки кода:

Настройка Плагин Эффект
seo_plugin (yoast/rankmath/none) Yoast SEO или Rank Math Подключено к коду. При создании записи LAGR_SEO::apply_meta_description() пишет сгенерированное краткое описание (первые ~30 слов, без тегов) напрямую в meta-поле выбранного плагина: _yoast_wpseo_metadesc для Yoast, rank_math_description для Rank Math. none — ничего не пишет.
integration_ratings — (свой код, см. раздел 8г) Подключено. С 0.3.0 больше не про сторонний плагин — гейтит вставку собственного блока lagr/rating в карточку и single-страницу (LAGR_Patterns::ratings_block()). Выключено — рейтинг просто не попадает в разметку паттерна при регистрации.
integration_related_posts — (свой код, см. раздел 8г) Подключено. С 0.3.0 больше не про сторонний плагин — гейтит вставку собственного блока lagr/related-posts на single-странице (LAGR_Patterns::related_posts_block()).
integration_filter_everything Filter Everything НЕ подключено, всё ещё вручную. Боковая колонка с [fe_widget] вшита напрямую во все 4 архивных шаблона в install-templates.php, без проверки этого флага. На сайте без Filter Everything это даст пустую колонку с нерабочим шорткодом; при переносе на другой сайт сети — либо ставить и настраивать Filter Everything так же (раздел 8в), либо убрать колонку из шаблона.
integration_external_links WP External Links Флаг-документация, не читается кодом: подтверждает, что внешние ссылки в контенте не нужно вручную помечать target=_blank/rel=nofollow — это уже делает сторонний плагин автоматически, независимо от значения флага.
integration_relevanssi Relevanssi Флаг-документация, не читается кодом: Relevanssi сам перехватывает поиск WordPress через собственные хуки, как только активен и настроен (раздел про /tag/x/ и поиск в разделе 1) — переключатель ничего не включает и не выключает программно.

LAGR_Settings::integration_enabled( 'ratings' ) — обёртка над get( 'integration_ratings' ), используется в class-lagr-patterns.php.

Реально читаются кодом на 2026-09-04: seo_plugin, integration_ratings, integration_related_posts — все три меняют фактическую разметку паттернов. integration_filter_everything/integration_external_links/integration_relevanssi — флаги-заготовки/документация, ничего не переключают программно (см. пояснения в таблице выше).


8б. Паттерны (class-lagr-patterns.php)

Реализация макетов из docs/mockups/github-repo-pattern-*.html в виде настоящих блочных паттернов (register_block_pattern()), доступных в инсертере редактора под категорией «GitHub Repo Collector». Как и договаривались — это только вставляемые заготовки: после вставки в шаблон/страницу через редактор паттерн превращается в обычные, полностью редактируемые блоки. Сама привязка паттернов к реальным шаблонам сайта (архив, single, главная) — отдельный, осознанный шаг, не выполняется автоматически при регистрации.

Паттерн Что это
lagr/repo-card Одна карточка репозитория — содержимое Post Template внутри core/query (Query Loop). Единственный источник разметки карточки — переиспользуется в остальных паттернах ниже, без копипасты.
lagr/archive-grid Сетка карточек для архива темы/языка/лицензии — core/query с inherit:true (наследует запрос текущего архива) + пагинация + состояние «ничего не найдено».
lagr/homepage-tiles-top Плитка на главную, 6 карточек, сортировка по _gh_score (см. ниже про query_loop_block_query_vars).
lagr/homepage-tiles-new Плитка на главную, 6 карточек, сортировка по дате.
lagr/tag-and-topic-cloud Два core/tag-cloud рядом — repo_topic и post_tag.
lagr/hero-search core/search + быстрые ссылки на топ-6 самых крупных тем (реальные термины repo_topic, не статичный текст).
lagr/single-repo-layout Полная разметка одиночной записи — шапка, рейтинг, статистика, core/post-content + боковая колонка, похожие записи.

Сортировка по score в Query Loop. Штатный инспектор блока «Цикл запроса» не умеет сортировать по произвольному meta-полю — только по фиксированному набору (дата/заголовок/случайно). Решение: в JSON-атрибут query паттерна homepage-tiles-top добавлен собственный ключ lagrOrderBy: "score" (WP не отбрасывает незнакомые ключи в объекте query, просто их игнорирует при стандартной сборке $query_args) — и хук query_loop_block_query_vars в LAGR_Patterns::filter_score_order() перехватывает именно эту комбинацию и подставляет meta_key=_gh_score, orderby=meta_value_num.

Гейтинг по интеграциям. Блок рейтинга (wp-postratings/ratings — почему не шорткод, см. ниже) и блок «Похожие репозитории» ([crp], Contextual Related Posts) вставляются в паттерн только если соответствующий тумблер в настройках включён (LAGR_Settings::integration_enabled()) — проверяется в момент регистрации паттерна на PHP-стороне, а не в браузере.

Важная находка при реализации: _gh_* meta и Block Bindings

Все meta-поля репозитория названы с ведущим подчёркиванием (_gh_stars и т.д.) — стандартная WP-конвенция, чтобы поле не показывалось в дефолтном метабоксе «Произвольные поля» (у нас свои метабоксы). Но у этой конвенции есть побочный эффект: is_protected_meta() считает любое поле с _ защищённым, а источник core/post-meta в Block Bindings явно отказывается отдавать защищённые поля — независимо от show_in_rest. Без учёта этого все привязки на паттернах молча показывали бы placeholder вместо реальных данных (проверено вживую: до фикса карточка показывала «0» звёзд и слово «owner» вместо реального ника, хотя в БД лежали верные значения).

Исправлено точечным фильтром в LAGR_Meta_Fields::unprotect_for_bindings() (хук is_protected_meta) — снимает защиту только с полей из repo_meta_keys(), ничего не меняет для остальных _-полей сайта.

Паттерны вставлены в реальные шаблоны сайта

Шесть шаблонов установлены как записи wp_template (тема twentytwentyfive) через разовый скрипт (не входит в сам плагин — миграция выполнена вручную при внедрении):

Шаблон Содержимое
archive-github_repo core/query-title + core/term-description + lagr/archive-grid
taxonomy-repo_topic / taxonomy-repo_language / taxonomy-repo_license то же самое — один и тот же archive-grid работает для всех, т.к. Query Loop там с inherit:true
single-github_repo lagr/single-repo-layout
front-page lagr/hero-search → lagr/homepage-tiles-top → lagr/tag-and-topic-cloud → lagr/homepage-tiles-new → блок «Из блога» (обычный core/query по post)

front-page.html перекрывает главную независимо от настройки «Показывать на главной странице» в Настройках → Чтение — is_front_page() истинен что при «последних записях», что при «статической странице», поэтому этот шаблон сработал без изменения Reading Settings; блог как был на /-корне в виде home.html, так и остался бы там, если бы не было front-page.html (теперь достаточно приоритетен и используется вместо него для главной).

Важное ограничение: контент шаблона — это одноразовый снимок, не живая ссылка на паттерн. Once вставлен в wp_template, PHP-код паттерна больше не перевызывается для этого шаблона — правки в class-lagr-patterns.php (новое поле в карточке, другой текст) НЕ появятся на сайте, пока шаблон не переустановить заново (пересобрать контент через WP_Block_Patterns_Registry и обновить wp_template пост). Это ровно то же поведение, что и при вставке паттерна вручную через редактор — паттерн становится обычным контентом. На это напоролись при реализации: попытка показать «популярные темы» на главной через get_terms() в PHP на момент регистрации паттерна заморозила список на состоянии базы в момент установки шаблона — почему в итоге это заменено на настоящий динамический блок core/tag-cloud (см. код hero_search_markup()) вместо пре-вычисленной строки.

Баг рейтинга внутри Query Loop: [ratings] показывал один и тот же ID на всех карточках

При первой живой проверке плитки на главной (после публикации реальных данных) все карточки показывали один и тот же ID рейтинга — причём чужой, совпадающий с ID первой записи блога, а не текущего репозитория. Причина — в самом устройстве core/shortcode: этот блок НЕ выполняет [ratings] сразу (render_block_core_shortcode() в ядре WP лишь оборачивает текст в wpautop()), реальное разворачивание шорткода происходит позже, одним проходом do_shortcode() по уже собранному HTML всей страницы — то есть уже ПОСЛЕ того, как core/post-template завершил перебор карточек и вызвал wp_reset_postdata(). К этому моменту глобальный $post/$id (на них опирается легаси-функция the_ratings()) указывает на «внешний» пост страницы, один и тот же для всех вхождений [ratings] разом.

Исправлено переходом с шорткода на собственный нативный блок плагина — wp-postratings/ratings (WP-PostRatings его регистрирует наравне с шорткодом, class-wp-postratings-blocks.php). Блок — полноценный динамический блок ядра Gutenberg, рендерится синхронно в момент прохода core/post-template по каждому элементу — так же, как core/post-title/core/post-terms, поэтому корректно видит текущий пост цикла. Проверено вживую: 12 карточек на главной — 12 разных, верных ID.

Ещё две правки, потребовавшиеся ради паттернов

  • _gh_url — новое meta-поле (https://github.com/{full_name}), нужно было для привязки url кнопки «GitHub →» (Block Bindings не умеет конкатенировать строки, только отдавать готовое значение поля). Для 100 уже собранных до этой правки записей потребовался разовый бэкфилл (без обращений к GitHub API — вычисляется из уже сохранённого _gh_full_name).
  • post_date/post_modified теперь берутся из created_at/pushed_at репозитория, а не из момента сбора — иначе core/post-date в паттернах показывал бы дату импорта, а не реальную свежесть репозитория на GitHub.

8в. Filter Everything — боковой фильтр архива

Настроен через админку плагина Filter Everything (не через наш плагин) — «Набор фильтров» (CPT filter-set, пост #409):

Фильтр Критерий Вид
Теги таксономия post_tag чекбоксы
Звёзды GitHub meta _gh_stars, тип «Числа» числовой диапазон (слайдер)

«Язык» и «Лицензия» были в наборе изначально, убраны 2026-09-04 вслед за выключением collect_language/collect_license (раздел 4а) — иначе фильтр показывал бы рабочие чекбоксы для полей, которых больше нет ни на карточках, ни на single-странице.

Локация: «Common WordPress pages» → «All archive pages for this Post Type» на типе записи github_repo — один набор фильтров автоматически покрывает архив CPT (/repos/) и все три его таксономии (/repo-topic/x/, /repo-language/x/, /repo-license/x/), без необходимости создавать 4 разных набора.

Вставлен в шаблоны шорткодом [fe_widget] внутри core/columns (22% сайдбар / 78% сетка) — см. install-templates.php. Проверено вживую: клик по чекбоксу реально фильтрует список через AJAX (не просто рисует чекбокс).

Известное ограничение: это НЕ управляется тумблером integration_filter_everything из настроек плагина (см. раздел 8а) — шорткод вшит в шаблон напрямую. На сайте сети без Filter Everything колонка отрисуется, но фильтр не заработает.

При редактировании набора фильтров через админку Filter Everything — осторожно с кнопками «Удалить». На экране их две с одинаковым текстом: (1) ссылка в панели «Опубликовать» отправляет в корзину весь набор фильтров; (2) кнопка внутри развёрнутой строки конкретного фильтра (button.wpc-button-link-delete) удаляет только её, но требует подтверждения второй кнопкой «Удалить, я уверен» — и после такого удаления обязательно нажать «Обновить» всей формы, иначе фронтенд-виджет [fe_widget] рендерится пустым (изменения на бэкенде уже применились, но публичный вывод не пересобирается без полного сохранения).


8г. Свой рейтинг и «Похожие репозитории» (class-lagr-ratings.php, class-lagr-related.php) — с версии 0.3.0

Решение 2026-09-04: WP-PostRatings и Contextual Related Posts убраны из зависимостей плагина — не потому что были плохи, а потому что для этих двух функций объём собственной реализации оказался маленьким, а контроль/переносимость на 20 сайтов сети — выше. Filter Everything и Relevanssi (раздел 8в и раздел 1) остались сторонними — там объём для самостоятельной переделки уже несоразмерный.

Рейтинг (lagr/rating, класс LAGR_Ratings). Хранение — два postmeta-счётчика: _lagr_rating_total (сумма голосов), _lagr_rating_count (число голосов), среднее считается на лету. Инкремент — не get_post_meta() + update_post_meta() (это read-then-write: два одновременных голоса могут прочитать одно и то же стартовое значение, один голос потеряется), а прямой UPDATE wp_postmeta SET meta_value = meta_value + N WHERE post_id=... AND meta_key=... — атомарно на уровне строки в InnoDB. Голосование — клик по сердечку на фронтенде (blocks/rating/view.js, автоподключается через viewScript в block.json, без ручного wp_enqueue_script()) отправляет AJAX (wp_ajax_lagr_rate/wp_ajax_nopriv_lagr_rate) с nonce; один голос с браузера — через cookie lagr_voted_{post_id}, без другой защиты от накрутки (сознательно просто, как и счётчик кликов в partner-catalog).

Похожие репозитории (lagr/related-posts, класс LAGR_Related). Обычный get_posts() с tax_query по repo_topic (тому самому курируемому термину, всегда один на запись — более точный сигнал «похожести», чем generic keyword-matching CRP), отсортировано по _gh_stars. Оба блока — динамические (render.php, без сохранённой разметки) по той же причине, что и wp-postratings/ratings раньше (раздел «Баг рейтинга внутри Query Loop» ниже) — рендерятся синхронно за каждый элемент цикла, корректно работают внутри Query Loop.

Русская плюрализация («1 оценка» / «3 оценки» / «5 оценок») — своя функция ru_plural() (классические 3 формы), потому что штатная _n() в WordPress поддерживает только 2 формы (единственное/множественное) и для русского языка даёт неверный текст на «оценка/оценки/оценок».

Обычные записи блога (post) продолжают использовать WP-PostRatings как раньше — этот плагин теперь просто не установлен как зависимость llm-agent-github-repos/partner-catalog, но с сайта не убирался и не трогался.


9. WP-CLI (class-lagr-cli.php)

Регистрируется только если запущено под WP-CLI (defined('WP_CLI') && WP_CLI), не обязательно для работы плагина — альтернатива для сайтов, где есть SSH:

wp github-repos tick                 # прогнать тик (то же, что cron)
wp github-repos run --rule=<id>      # запустить конкретное правило прямо сейчас, игнорируя интервал
wp github-repos list-rules           # таблица всех правил: ID, включено, интервал, последний запуск

Важно для этого сервера (Beget): команда wp — shell-обёртка с зашитым путём на PHP 7.4, не сам .phar. Для действий, чувствительных к версии PHP (например, установка стороннего плагина с Requires PHP выше 7.4), вызывать .phar напрямую: php8.3 /usr/local/bin/wp-cli.phar ... — WP_CLI_PHP=... wp ... не всегда это перекрывает (проверено эмпирически). См. docs/stack-decisions.md.


10. Известные ограничения / открытые вопросы

  • Markdown→HTML fallback (без LLM) не разбирает списки/таблицы/блоки кода — только заголовки, bold, ссылки, параграфы. Осознанный минимализм, не баг.
  • Пагинация GitHub Search API за один запрос ограничена 100 результатами — если понадобится собирать больше 100 за прогон одного правила, нужна доработка (не в MVP).
  • «Топ по популярности» через реальные просмотры/лайки — WP Post Views Counter сейчас не используется (решили отказаться от счётчика просмотров), WP-PostRatings даёт рейтинг, но не количество посещений.