A complete set of skills for advanced development tools
SaferSkills independently audited telegram-skills (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Version: 1.8.0 Released: 2026-05-16 License: MIT
Note. This file is a high-level overview of the skill for human readers. The canonical skill prompt consumed by Claude Code lives at.claude/skills/botforge/SKILL.md. The raw prompt for other LLMs lives atsystem_prompt.txt. If this file and the canonical skill disagree, the canonical skill wins.
Главный тезис: AI генерирует код. BotForge заставляет AI инженерить продукты.
Что делает skill:
requests, секреты в кодеЦенность:
Полный system prompt вынесен в system_prompt.txt. Вставляется как есть в:
system messagerepositories/.DbSessionMiddleware, не импортом.states/, не inline.build_*_kb() в keyboards/; каждая кнопка имеет handler/URL/web_app target.integrations/<vendor>_client.py с httpx.AsyncClient, timeout ≤ 10s, tenacity retry (3 попытки, exp. backoff).pydantic_settings.BaseSettings, source of truth — .env.ruff + mypy --strict зелёныеprintconfig/constants.pyErrorsMiddleware: traceback + user_id + update_type + request_idtenacity.retry(stop=stop_after_attempt(3), wait=wait_exponential()) + graceful fallbackasync with session.begin():, rollback автоматический.env; .env.example без значенийADMIN_IDS + AdminFiltersecret_token обязателенCallbackData-фабрики вездеdocker-compose.yml: bot + postgres + redis + nginx (при webhook)upgrade head в entrypoint.shMakefile: run, test, lint, migrate, up, down, logs, deploy/start, bot menu, deep links, главное меню, вложенные экраны, платежи, админка, back/home/cancel пути.call.answer() и по возможности редактируют текущее сообщение, а не спамят новыми меню.README.md: что это / stack / local run / env / deploy / архитектураdocs/ADR/NNNN-title.md для крупных решенийdocs/RUNBOOK.md для инцидентов### 1. Бизнес-брифинг
(5 вопросов или пропуск)
### 2. ADR
Стек, модель данных, карта навигации, зависимости, деплой, риски, точки расширения.
### 3. Дерево проекта
<tree>
### 4. Файлы (в порядке зависимости)
<config → … → infra>
### 5. Self-review
- [x] ...
### 6. Запуск
<deploy commands>### План изменений
Слои: ...
Новые файлы: ...
Изменяемые: ...
Обратная совместимость: сохраняется.
### Миграция (если нужно)
### Код (только затронутые файлы)
### Self-review дельты[blocker] app/handlers/payment.py:42 — прямой SQL; вынести в PaymentRepo.create()
[major] app/services/broadcast.py:88 — нет retry на bot.send_message
[major] app/keyboards/main.py:18 — кнопка «Каталог» ведёт в callback без handler-а
[minor] app/keyboards/main.py:12 — inline-клавиатура собирается в handler
[nit] app/config/settings.py:5 — отсутствует docstring### Инвентаризация
### План миграции (по шагам, без простоя)
### Порядок PR-ов (атомарные шаги)
### Риски и откат### Режим (polling → webhook)
### Изменения (bot/__main__.py, nginx, compose, env)
### Деплой (конкретные команды)
### Откатmy_bot/
├── app/
│ ├── __main__.py
│ ├── bot/
│ │ ├── dispatcher.py
│ │ └── lifespan.py
│ ├── config/
│ │ ├── settings.py
│ │ ├── logging.py
│ │ └── constants.py
│ ├── db/
│ │ ├── engine.py
│ │ └── uow.py
│ ├── models/
│ │ ├── base.py
│ │ ├── user.py
│ │ ├── subscription.py
│ │ └── payment.py
│ ├── schemas/
│ ├── repositories/
│ │ ├── base.py
│ │ ├── user_repo.py
│ │ ├── subscription_repo.py
│ │ └── payment_repo.py
│ ├── services/
│ │ ├── user_service.py
│ │ ├── subscription_service.py
│ │ ├── payment_service.py
│ │ ├── broadcast_service.py
│ │ └── channel_check_service.py
│ ├── integrations/
│ │ ├── yookassa_client.py
│ │ ├── openai_client.py
│ │ ├── sheets_client.py
│ │ └── wordpress_client.py
│ ├── middlewares/
│ │ ├── db_session.py
│ │ ├── throttling.py
│ │ ├── auth.py
│ │ ├── i18n.py
│ │ └── logging.py
│ ├── filters/
│ │ ├── admin.py
│ │ └── subscription.py
│ ├── keyboards/
│ │ ├── inline/
│ │ └── reply/
│ ├── states/
│ ├── handlers/
│ │ ├── __init__.py
│ │ ├── common.py
│ │ ├── subscription.py
│ │ ├── payment.py
│ │ ├── admin/
│ │ └── errors.py
│ └── utils/
├── migrations/
├── tests/
│ ├── conftest.py
│ ├── unit/
│ └── integration/
├── docs/
│ ├── ADR/
│ └── RUNBOOK.md
├── docker/
│ ├── Dockerfile
│ └── entrypoint.sh
├── docker-compose.yml
├── docker-compose.prod.yml
├── .env.example
├── .gitignore
├── .dockerignore
├── pyproject.toml
├── alembic.ini
├── Makefile
└── README.md# app/config/settings.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
bot_token: str
admin_ids: list[int]
required_channels: list[int] = []
database_url: str
redis_url: str = "redis://redis:6379/0"
webhook_url: str | None = None
webhook_secret: str | None = None
webhook_path: str = "/tg/webhook"
yookassa_shop_id: str | None = None
yookassa_secret_key: str | None = None
openai_api_key: str | None = None
log_level: str = "INFO"
settings = Settings()# app/db/engine.py
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from app.config.settings import settings
engine = create_async_engine(settings.database_url, pool_pre_ping=True)
session_factory = async_sessionmaker(engine, expire_on_commit=False)# app/middlewares/db_session.py
from aiogram import BaseMiddleware
from app.db.engine import session_factory
class DbSessionMiddleware(BaseMiddleware):
async def __call__(self, handler, event, data):
async with session_factory() as session:
data["session"] = session
return await handler(event, data)# app/repositories/base.py
from sqlalchemy.ext.asyncio import AsyncSession
class BaseRepo:
def __init__(self, session: AsyncSession) -> None:
self.session = session# app/services/user_service.py
from aiogram.types import User as TgUser
from app.repositories.user_repo import UserRepo
class UserService:
def __init__(self, user_repo: UserRepo) -> None:
self._users = user_repo
async def ensure_user(self, tg_user: TgUser) -> None:
await self._users.upsert(
tg_id=tg_user.id,
username=tg_user.username,
lang=tg_user.language_code,
)# app/services/channel_check_service.py
from aiogram import Bot
from redis.asyncio import Redis
class ChannelCheckService:
def __init__(self, bot: Bot, redis: Redis, channels: list[int]) -> None:
self._bot, self._redis, self._channels = bot, redis, channels
async def is_subscribed(self, user_id: int) -> bool:
key = f"subcheck:{user_id}"
if (cached := await self._redis.get(key)) is not None:
return cached == b"1"
for chat_id in self._channels:
m = await self._bot.get_chat_member(chat_id, user_id)
if m.status in {"left", "kicked"}:
await self._redis.set(key, "0", ex=600)
return False
await self._redis.set(key, "1", ex=600)
return True# app/middlewares/throttling.py
from aiogram import BaseMiddleware
from redis.asyncio import Redis
class ThrottlingMiddleware(BaseMiddleware):
def __init__(self, redis: Redis, rate: float = 1.0) -> None:
self._redis, self._rate = redis, rate
async def __call__(self, handler, event, data):
uid = getattr(event.from_user, "id", None)
if uid is None:
return await handler(event, data)
key = f"thr:{uid}"
if await self._redis.set(key, "1", ex=int(self._rate), nx=True):
return await handler(event, data)# app/filters/admin.py
from aiogram.filters import BaseFilter
from aiogram.types import TelegramObject
from app.config.settings import settings
class AdminFilter(BaseFilter):
async def __call__(self, event: TelegramObject) -> bool:
uid = getattr(event.from_user, "id", None)
return uid in settings.admin_ids# app/services/broadcast_service.py
import asyncio
from aiogram import Bot
from aiogram.exceptions import TelegramRetryAfter, TelegramForbiddenError
class BroadcastService:
def __init__(self, bot: Bot, rps: int = 25) -> None:
self._bot, self._sem = bot, asyncio.Semaphore(rps)
async def send_to(self, user_ids: list[int], text: str) -> dict[str, int]:
ok = blocked = failed = 0
async def _one(uid: int) -> None:
nonlocal ok, blocked, failed
async with self._sem:
try:
await self._bot.send_message(uid, text)
ok += 1
except TelegramRetryAfter as e:
await asyncio.sleep(e.retry_after)
await self._bot.send_message(uid, text); ok += 1
except TelegramForbiddenError:
blocked += 1
except Exception:
failed += 1
await asyncio.sleep(1 / 25)
await asyncio.gather(*[_one(u) for u in user_ids])
return {"ok": ok, "blocked": blocked, "failed": failed}# app/states/onboarding.py
from aiogram.fsm.state import State, StatesGroup
class Onboarding(StatesGroup):
waiting_name = State()
waiting_email = State()
confirm = State()Каждая кнопка должна вести в handler, URL или Mini App. Вложенные экраны держат Назад/Главное меню, callback-и собираются через компактные CallbackData, а подписи читаются без опоры на emoji.
# app/keyboards/inline/main_menu.py
from aiogram.filters.callback_data import CallbackData
from aiogram.types import InlineKeyboardButton as B, InlineKeyboardMarkup as K
class MenuCb(CallbackData, prefix="menu"):
screen: str
def main_menu_kb() -> K:
return K(inline_keyboard=[
[B(text="Каталог", callback_data=MenuCb(screen="catalog").pack())],
[B(text="VIP", callback_data=MenuCb(screen="vip").pack()),
B(text="Профиль", callback_data=MenuCb(screen="profile").pack())],
])
def back_home_kb(back_to: str = "main") -> K:
return K(inline_keyboard=[
[B(text="Назад", callback_data=MenuCb(screen=back_to).pack())],
[B(text="Главное меню", callback_data=MenuCb(screen="main").pack())],
])# app/handlers/errors.py
import logging, uuid
from aiogram import Router
from aiogram.types import ErrorEvent
router = Router(name="errors")
log = logging.getLogger(__name__)
@router.errors()
async def on_error(event: ErrorEvent) -> bool:
rid = uuid.uuid4().hex[:8]
log.exception("update failed", extra={"request_id": rid})
upd = event.update
target = upd.message or (upd.callback_query.message if upd.callback_query else None)
if target:
await target.answer(f"Что-то пошло не так. Код: {rid}")
return TrueЗапрос: gated-контент + VIP за 299 ₽/мес + рассылки + админка. Stack: aiogram 3 + PostgreSQL + Redis + Docker. Webhook за nginx. Telegram Stars + ЮKassa. Кэш getChatMember 10 мин. Broadcast 25 msg/s. Модель: users, subscriptions(plan, status, expires_at), payments(provider, ext_id), content_items(tier).
Запрос: OpenAI-бот, 3 тарифа, лимиты токенов, история диалогов. OpenAI через integrations/openai_client.py (httpx + tenacity). История в messages. Счётчики лимитов в Redis. Переполнение → upsell.
Запрос: FSM-сбор заявки → Google Sheets → уведомление админу. FSM: Lead.name → phone → goal → confirm. Валидация regex. Транзакция: insert в leads + append в Sheets + admin-notify. Outbox-pattern на случай частичных сбоев.
Полные реализации см. в examples/.
.claude/skills/botforge/SKILL.md.cursorrulesAGENTS.mdsystem messageBotForge: [Lite|Pro|Media|SaaS]
Задача: <бизнес-описание>
Ограничения: <бюджет/хостинг/сроки>
Ответы на брифинг (опционально): 1)... 2)...BotForge: Pro. Бот-витрина курсов с оплатой ЮKassareview app/services/payment_service.pyrepositories/.env.example полныйruff и mypy --strict зелёные.env на сервере, не в репоBOT_TOKEN свежийAdminFiltersecret_token включёнCallbackData-фабрики везде| Версия | Статус | Содержание |
|---|---|---|
| v1.8.0 | released | universal agent compatibility pack: root AGENTS.md, Copilot, Gemini, Windsurf, Cline, Continue, Aider, Junie, Zed adapters and validation |
| v1.7.2 | released | Bot API 10.0 baseline, non-overridable safety bans, stronger sync/version/golden validation |
| v1.7.1 | released | web admin panel (React + FastAPI + SSE), /botforge-admin-web command, 23 references |
| v1.7 | released | stability protocols (Bypass / Override / Recovery), anti-patterns, naming contract |
| v1.6 | released | admin panel reference, analytics, GDPR compliance, anti-spam |
| v1.5 | released | performance, groups & channels, media, inline mode deep-dives |
| v1.4 | released | observability (structlog / Sentry / Prometheus), scheduler, i18n, subscriptions |
| v1.3 | released | Mini Apps, auth (initData HMAC / OAuth / API keys / roles) |
| v1.2 | released | unified payments (Stars / ЮKassa / CryptoBot / Stripe / Tribute) |
| v1.1 | released | examples pack, four-format sync, golden tests |
| v1.0 Pro | released | core skill, aiogram 3, Postgres, Redis, Docker, Alembic, admin, broadcast, channel-check |
| v1.9 Factory | planned | CLI botforge new <name>, multitenancy |
| v2.0 Studio | vision | UI-конструктор → экспорт проекта |
Подробности релизов — в docs/CHANGELOG.md.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.