git log CHANGELOG source files Git history SKILL.md 1. Сбор материала 2. Структура поста 3. Правила стиля 4. HTML + CDN 5. Финальный чеклист blog.epich.ru Mermaid-схемы hljs + fullscreen Habr-стиль HTML nginx static Источники Инструкция для AI Готовая статья
Путь от коммитов до опубликованного HTML-поста через SKILL.md

С чего всё начиналось

Монорепозиторий проекта состоит из 18 сервисов, каждый из которых живёт в своём поддомене. Есть мультиплеерные игры на Go, статические SPA, сервис аутентификации, поддержка пользователей и совместный просмотр видео через WebRTC. Инфраструктура держится на Docker Compose и Traefik. Разработка ведётся совместно с AI-агентом через Copilot Chat в VS Code.

После нескольких месяцев работы накопилось около 150 коммитов с реально интересными решениями: единая страница ошибок через Traefik middleware, non-root пользователи во всех Go-контейнерах, система "активного знания" которая превращает любое решение в автопроверку при коммите. Всё это осело в git-истории и нигде не было описано.

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

Контекст для читателей из Habr: в VS Code Copilot Chat поддерживает "скиллы": специальные файлы SKILL.md в репозитории, которые загружаются в контекст перед выполнением задачи. По сути это очень длинные и структурированные промпты с чеклистами, примерами и запретами.

Анатомия скилла

Файл скилла живёт в служебном каталоге репозитория .github/skills/blog-post/. Внутри лежат frontmatter с именем и описанием, а затем шесть разделов. Каждый раздел оформлен как инструкция с checkboxes и примерами кода, которую агент воспринимает как обязательный протокол.

---
name: blog-post
description: "Создать статью-кейс в стиле Habr по итогам текущей сессии..."
---

## Шаг 1 - Собрать материал

### Источники (читать все перед написанием)

```bash
git log --oneline -20
git show <hash> --stat
git diff HEAD~N HEAD -- domains/<domain>/
```

Также читать: TODO.md, README.md, исходники изменённых файлов.

**Что извлечь:**
- Исходная проблема или нестандартная техническая цель
- Конкретные числа: размер файлов, строки, время, запросы/сек
- Компромиссы и решения, которые были отброшены
graph TD A["Пользователь: напиши пост про X"] --> B["Агент загружает SKILL.md"] B --> C["Шаг 1: git log + TODO + исходники"] C --> D["Шаг 2: структура поста"] D --> E["Шаг 3: применить правила стиля"] E --> F["Шаг 4: создать HTML с CDN"] F --> G["Шаг 5: чеклист из 12 пунктов"] G --> H["blog.epich.ru/public/slug.html"] style B fill:#6c5ce7,color:#fff style H fill:#2c8f4d,color:#fff
Пайплайн от запроса пользователя до HTML-файла на сервере

Сбор материала как первый шаг

Ключевая идея в том, что агент всегда начинает с чтения, а не с написания. Скилл явно предписывает запустить git log --oneline -20, потом git show для интересных коммитов, а затем прочитать список задач затронутых доменов. Только после этого агент приступает к тексту.

Это важно по одной причине: AI очень хорошо генерирует общие слова про архитектуру, но конкретные цифры берёт только если они были в контексте. Когда агент читает git show a4a7666 --stat и видит "unit-тесты: +11 тестов (было 66 -> стало 77)" или "2 файла, 163 строки добавлено", эти числа попадают в статью. Без явного предписания агент их пропускает.

Из материала скилл предписывает извлечь пять конкретных вещей: исходную проблему, ограничения и мотивацию, подсистемы решения, конкретные числа и компромиссы. Списком, с bullet-points, чтобы агент прошёлся по каждому пункту и нашёл соответствующий материал в истории коммитов.

Правила стиля как технический контракт

Раздел "Правила написания" занимает примерно треть всего файла. Там два блока: запреты на конкретные конструкции и список запрещённых AI-клише. Запреты сформулированы максимально машиночитаемо, как lint-правила:

Строгие запреты:

- Запрещены предложения из 1-3 слов
- Запрещено использовать "—" (тире-разделитель)
- Запрещено использовать классические кавычки-ёлочки
- Запрещено употреблять сокращения вроде "т.к.", "т.е.", "и др."
- Запрещено олицетворение (персонификация объектов)
- Запрещена метафора внутри одной подсистемы
- Запрещён синтаксический параллелизм
- Минимум частиц "не" и приставок "не-" в тексте

Запрещённые AI-клише идут отдельным списком: "важно отметить", "в заключение", "в современном мире", "таким образом", "следует отметить". Это те фразы, которые особенно ярко выдают машинно-сгенерированный текст на Habr.

Практическое наблюдение: запрет на тире-разделитель (--) и ёлочки поначалу кажется странным, но он работает. Эти символы характерны именно для "причёсанного" AI-текста. После запрета предложения становятся более разговорными, потому что агенту приходится переформулировать конструкции, которые он обычно строит через тире.

Технический стек для вывода

Блоговый домен работает на nginx alpine и раздаёт статику из директории public/. Здесь нет SSR и нет отдельного билд-шага. Каждая статья живёт как самостоятельный HTML-файл. Это накладывает конкретное ограничение: все зависимости должны лежать внутри файла или подключаться через CDN.

Скилл прописывает три CDN-библиотеки: highlight.js для подсветки кода, Mermaid для диаграмм, и стили из Cloudflare CDN для highlight.js темы. Инициализация двух библиотек через единый обработчик DOMContentLoaded, чтобы они гарантированно запускались после рендера DOM:

<!-- Подсветка кода -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>

<!-- Mermaid-диаграммы -->
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>

<script>
  document.addEventListener('DOMContentLoaded', () => {
    hljs.highlightAll();
    mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
  });
</script>

Отдельно прописан механизм fullscreen для схем и SVG-иллюстраций. Каждая диаграмма оборачивается в figure.zoomable, клик по которой открывает overlay с фиксированным позиционированием. Схему можно рассмотреть детально без скролла страницы.

sequenceDiagram participant U as Пользователь participant C as Copilot Chat participant S as SKILL.md participant G as Git participant F as Файловая система U->>C: "напиши пост про blog-post скилл" C->>S: читает .github/skills/blog-post/SKILL.md C->>G: git log --oneline -20 G-->>C: список коммитов C->>G: git show 5986da0 --stat G-->>C: детали коммита C->>F: читает TODO.md, README.md F-->>C: контекст домена C->>F: создаёт domains/blog.epich.ru/public/slug.html F-->>U: статья готова
Sequence diagram: взаимодействие агента с инструментами при создании поста

Связь с системой "активного знания"

Скилл blog-post не появился в вакууме. В той же сессии был создан скилл memorize, который описывает как превращать любые договорённости из диалога в устойчивые части репозитория. Разница в природе артефакта: memorize фиксирует правило которое будет применяться автоматически при каждом коммите или редактировании, а blog-post создаёт публичный документ для внешней аудитории.

Тип знания Механизм фиксации Когда применяется
Соглашение по коду .github/instructions/*.instructions.md При редактировании нужных файлов
Автопроверка precommit-check.sh + unit-тест При каждом коммите
Workflow / процесс .github/skills/*/SKILL.md При активной работе по скиллу
Инженерный кейс blog.epich.ru/public/*.html Публичная документация

Эта классификация позволяет ответить на вопрос "куда положить знание" независимо от его природы. Конвенция о non-root пользователях в Dockerfile ушла в блокирующую проверку precommit-check. Алгоритм создания нового домена ушёл в отдельный скилл new-domain. Рассказ о том, как это всё строилось, идёт в блог.

graph LR K["Новое знание"] --> Q{"Тип?"} Q -->|"Можно grep'нуть нарушение"| P["precommit-check.sh"] Q -->|"Правило для конкретного\nтипа файлов"| I[".instructions.md"] Q -->|"Пошаговый workflow"| SK["SKILL.md"] Q -->|"Интересный инженерный кейс"| B["blog.epich.ru"] P --> C1["Блокирует коммит при нарушении"] I --> C2["Появляется в контексте при редактировании"] SK --> C3["Загружается перед задачей"] B --> C4["Публичная документация"] style K fill:#4a88c7,color:#fff style B fill:#2c8f4d,color:#fff
Дерево решений: куда записывать знание в зависимости от его природы

Что получилось в итоге

Скилл занял 130 строк. Создание этой статьи заняло одну команду в чат: агент прочитал 7 коммитов, изучил два скилла, вытащил конкретные цифры из git show, написал текст по 14 правилам стиля и сохранил HTML с работающим Mermaid и подсветкой кода.

130 строк в SKILL.md
6 обязательных шагов
14 запрещённых конструкций
0 ручных правок в тексте

Скилл blog-post зарегистрирован в инструкциях Copilot и в таблице скиллов внутри audit-customizations. Это значит, что precommit-check проверяет существование такого скилла в папке и наличие записи в индексе, иначе коммит блокируется.

Ключевое ограничение конструкции: скилл работает только если модель читает его перед написанием, а не генерирует текст из общих соображений. В VS Code Copilot это работает через механизм "skills" в виде SKILL.md файлов, которые загружаются по имени. Для openai-совместимых API придётся передавать содержимое файла явно в системный промпт.

Дальнейший план состоит в том, чтобы добавить в скилл автоматическое обновление индекса блога и черновой режим, который мы использовали в этот раз: файл создаётся, но ссылка на него в главном индексе блога остаётся закрытой.