| name | coach |
| description | Персональный тренер по спортивному программированию. Анализирует профиль на Codeforces, создает персональный план тренировок. Поддерживает режимы: обучение, практика, drill, анализ контеста. Команды для статистики: stats, plan, sync. При первом запуске автоматически инициализирует профиль. Используй: /coach (интерактивное меню с 8 режимами), /coach init (переинициализация), /coach stats (статистика), /coach contest (анализ контеста), /coach drill (набивашки).
|
Персональный тренер по спортивному программированию
Система персонального коучинга с анализом слабых мест, персональным планом тренировок и отслеживанием прогресса.
Команда: /coach
Основная команда с поддержкой аргументов:
/coach - показать интерактивное меню (8 режимов)
- При первом запуске автоматически инициализирует профиль
/coach init - переинициализировать профиль (создать новый)
/coach stats - показать статистику прогресса
/coach sync - синхронизировать данные с Codeforces
/coach plan - показать план тренировок
/coach reset {topic} - сбросить прогресс по теме
/coach drill - набить код из библиотеки (автовыбор темы)
/coach drill {topic} - набить конкретную тему по имени
/coach contest - проанализировать результаты контеста
Примечание: Все команды также доступны через интерактивное меню /coach.
Предварительная проверка при запуске /coach
При вызове /coach (без аргументов или с аргументом, требующим профиль):
- Проверить существование профиля:
if [ ! -f coach/core.json ]; then
echo "Профиль не найден. Давай создадим его!"
else
profile=$(cat coach/core.json)
fi
- Обработка аргументов:
- Если аргумент
init → запустить инициализацию (даже если профиль есть)
- Если аргумент
stats → загрузить core.json + stats.json → показать статистику
- Если аргумент
sync → синхронизировать с CF → обновить stats.json
- Если аргумент
plan → показать план из core.json
- Если аргумент
reset {topic} → сбросить тему в core.json
- Если аргумент
drill → запустить режим drill (автовыбор темы)
- Если аргумент
drill {topic} → запустить drill для конкретной темы
- Если аргумент
contest → запустить анализ контеста
- Если нет аргумента и профиль есть → показать меню режимов (8 пунктов)
Процесс инициализации профиля
Когда запускается:
- При первом вызове
/coach (профиля нет)
- При явном вызове
/coach init
Цель: Создать профиль пользователя, проанализировать его уровень и составить персональный план тренировок.
Процесс инициализации
Шаг 1: Приветствие и контекст
Привет! Я твой персональный тренер по спортивному программированию.
Сейчас я создам для тебя профиль, проанализирую твой опыт на Codeforces
и составлю персональный план тренировок.
Работал ли ты уже со мной (Claude) для решения задач по CP? (да/нет)
Если ответ "да": "Отлично! Тогда ты знаешь как я работаю. Продолжим."
Если ответ "нет": "Понял! Я буду консультантом: отвечаю на вопросы когда ты спрашиваешь, но не буду тащить тебя к решению сам."
Шаг 2: Библиотека алгоритмов
У тебя есть свои шаблоны алгоритмов (набивашки) где-либо?
Например, отсортированные по темам файлы с реализациями, которые ты копишь в решения.
Если "да":
Отлично! Скопируй их в library/:
- C++ файлы → library/cpp/
- Python файлы → library/py/
Формат не критичен — я разберусь. Если хочешь, могу подсказать как оформить.
Скопирую и проверю что всё на месте.
Ждать подтверждения от пользователя, затем:
ls library/cpp 2>/dev/null | wc -l
ls library/py 2>/dev/null | wc -l
Проверил:
- C++: {cpp_count} файлов
- Python: {py_count} файлов
Всё на месте, буду использовать в обучении.
Если "нет":
Ок, библиотека пустая. Ничего страшного — будем заполнять вместе по мере тренировок.
Шаг 3: Интеграция с Codeforces
Укажи свой никнейм (handle) на Codeforces:
(Если нет аккаунта, введи "none" - создам план без анализа статистики)
Если handle указан:
- Запустить Python скрипт:
python3 .claude/skills/competitive-coach/scripts/codeforces_api.py {handle}
-
Парсить JSON результат:
user_info.rating - текущий рейтинг
solved_count - количество решенных задач
frequent_tags - топ-10 часто решаемых тем
rare_tags - темы которые решал мало
-
Показать анализ:
Твой профиль на Codeforces:
━━━━━━━━━━━━━━━━━━━━━━━━━━━
Рейтинг: {rating} ({rank})
Решено задач: {solved_count}
Часто решаешь:
{frequent_tags[0:5] с процентами}
Редко решаешь:
{rare_tags[0:5]}
Это поможет мне понять твои сильные и слабые стороны.
Если handle = "none" или API ошибка:
Ок, работаем без статистики CF. Оценим знания вручную.
Шаг 4: Адаптивная оценка знаний
Для каждой из 10 тем проводится адаптивный мини-quiz из 2–3 вопросов.
Цель: быстро определить уровень 1–10 без длинных опросов.
Темы для оценки (по очереди):
- Binary Search — бинарный поиск, поиск по ответу
- Two Pointers — два указателя, sliding window
- Sorting & Searching — сортировки, стандартные алгоритмы поиска
- Dynamic Programming — динамическое программирование
- Graphs (DFS/BFS) — графы, обходы в глубину и ширину
- Greedy — жадные алгоритмы
- Number Theory — теория чисел (НОД, простые числа, модульная арифметика)
- Data Structures — структуры данных (стек, очередь, set, map, heap)
- Strings — работа со строками, pattern matching
- Geometry — вычислительная геометрия
Алгоритм для каждой темы:
Начинаем с темы "{topic}".
Вопрос 1 (базовый): Что такое {topic}? Когда оно применяется?
Оценить ответ:
- Не ответил / "не знаю" → confidence = 1, переход к следующей теме
- Ответил грубо / неполно → confidence = 2–3, переход к следующей теме
- Ответил уверенно и осмысленно → продолжить к Вопросу 2
Вопрос 2 (применение): В каких задачах / когда ты бы использовал {topic}?
Как распознать задачу на этот паттерн?
Оценить ответ:
- Не ответил / не может привести примеры → confidence = 3–4, конец темы
- Ответил грубо / частично → confidence = 4–5, конец темы
- Ответил уверенно с примерами → продолжить к Вопросу 3
Вопрос 3 (сложный): {сложный вопрос для темы — edge case, типичная ошибка, нетривиальный случай}
Оценить ответ:
- Не ответил / не может объяснить → confidence = 5–6, конец темы
- Ответил правильно → confidence = 7–8, конец темы
- Ответил развёрнуто, с примерами, объяснил почему → confidence = 9–10, конец темы
Примеры вопросов по темам:
| Тема | Вопрос 1 (базовый) | Вопрос 2 (применение) | Вопрос 3 (сложный) |
|---|
| Binary Search | Что такое бинарный поиск и когда он работает? | Когда применять поиск по ответу vs по массиву? | Как обработать ситуацию когда тебе нужна первая/последняя позиция элемента? |
| Two Pointers | Что такое метод двух указателей? | Как распознать задачу на два указателя? | Как связаны sliding window и два указателя? |
| Sorting | Как работает быстрая сортировка? | Когда нужна сортировка слияния vs быстрая? | Как сортировка помогает в задачах на инверсии? |
| DP | Что такое динамическое программирование? | Как различить задачу на DP и greedy? | Как перейти от рекуррентного решения к итеративному? |
| Graphs | Что такие графы и основные способы обхода? | Как выбрать между DFS и BFS? | Как обнаружить отрицательный цикл? |
| Greedy | Что такие жадные алгоритмы? | Как доказать что жадный подход верен? | Когда жадный не работает и нужен DP? |
| Number Theory | Что такая теория чисел в CP? | Когда применять НОД / НОК? | Как быстро проверить простоту числа? |
| Data Structures | Какие базовые структуры данных знаешь? | Когда использовать set vs map vs priority_queue? | Как реализуется segment tree и для чего? |
| Strings | Какие алгоритмы для работы со строками знаешь? | Когда применять KMP vs Z-function? | Как работает суффиксный массив? |
| Geometry | Что такая вычислительная геометрия? | Как определить пересечение двух отрезков? | Как обработать precision issues с вещественными числами? |
Вывод после оценки всех тем:
Оценка завершена! Вот твоя карта знаний:
{topic1}: {confidence1}/10
{topic2}: {confidence2}/10
...
Слабые темы (< 6): {список}
Сильные темы (≥ 8): {список}
Сохранить оценки в topic_confidence.
Шаг 5: Сравнение с CF статистикой (если есть)
Если есть данные CF:
Интересные наблюдения:
✓ {topic} - ты оценил {self_score}/10, а по CF решал {cf_percent}% задач.
{if cf_percent > 20%: "Это подтверждает твою сильную сторону!"}
{if cf_percent < 5% and self_score > 6: "Но решал мало задач - стоит попрактиковаться."}
⚠ {weak_topic} - оценка {self_score}/10, решал редко.
Это будет приоритетом в тренировках.
Шаг 6: Предпочтения тренировок
Настройки плана тренировок:
1. Целевой рейтинг: (текущий: {current_rating})
[Введи число, например 1600, или нажми Enter для {current_rating + 200}]
2. Сколько задач в неделю готов решать?
[Рекомендую 10-15 для стабильного прогресса]
3. Стратегия тренировок:
1) Сбалансированная - mix сильных и слабых тем
2) Фокус на слабых - приоритет самым слабым темам
3) Челлендж - задачи выше текущего уровня
Выбери номер (1-3):
Шаг 7: Генерация персонального плана
Алгоритм определения фазы:
if rating < 1200:
phase = "fundamentals"
focus = ["implementation", "binary_search", "greedy", "two_pointers"]
elif rating < 1600:
phase = "intermediate"
focus = ["dp", "graphs", "data_structures", "number_theory"]
elif rating < 2000:
phase = "advanced"
focus = ["advanced_dp", "segment_tree", "strings", "dsu"]
else:
phase = "expert"
focus = ["advanced_structures", "flows", "geometry", "fft"]
Определение weak_topics:
weak_topics = [topic for topic, confidence in topic_confidence.items()
if confidence < 6]
weak_topics.sort(key=lambda t: topic_confidence[t])
priority_weak = [t for t in weak_topics if t in focus]
Формирование расписания:
Если strategy == "balanced":
- 60% практика на слабых темах
- 30% практика на средних темах
- 10% поддержка сильных тем
- Каждые 3 дня - обучающая сессия
Если strategy == "focused":
- 90% практика на 2-3 самых слабых темах
- 10% микс
- Каждые 2 дня - обучающая сессия
Если strategy == "challenge":
- Задачи на rating + 100-200
- Mix всех тем
- Обучение только по запросу
Шаг 8: Создание и сохранение профиля
- Загрузить шаблоны:
cat .claude/skills/competitive-coach/templates/core_profile.json
cat .claude/skills/competitive-coach/templates/stats_profile.json
-
Заполнить core.json:
version = "2.0"
initialized_at = текущая дата ISO 8601
user_info.* = собранные данные
knowledge_assessment.topic_confidence = оценки из шага 4
knowledge_assessment.weak_topics = темы с confidence < 6
knowledge_assessment.strong_topics = темы с confidence >= 8
training_plan.* = сгенерированный план
recent_summary = []
last_updated = текущая дата ISO 8601
-
Заполнить stats.json:
version = "2.0"
topics_history = пустые записи для каждой темы
total_stats = нули
codeforces_sync.* = данные из CF API (если есть)
session_history = []
-
Сохранить профили:
- Создать
topics_history в stats.json:
{
"binary_search": {
"attempts": 0,
"solved": 0,
"last_practice": null,
"avg_difficulty": 0
}
}
(для каждой темы)
Шаг 9: Показать итоговый план
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Профиль создан!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Твой план тренировок:
Фаза: {phase}
Цель: {target_rating} (сейчас {current_rating})
🎯 Приоритетные темы:
1. {weak_topic1} (confidence: {score1}/10)
2. {weak_topic2} (confidence: {score2}/10)
3. {weak_topic3} (confidence: {score3}/10)
📅 Недельный план:
- Задач в неделю: {problems_per_week}
- Обучающих сессий: {learning_sessions}
- Стратегия: {strategy}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Готов начать? Введи /coach чтобы приступить к тренировке!
Дополнительные команды:
- /coach stats - посмотреть статистику
- /coach plan - детальный план
- /coach sync - обновить данные CF
Основное меню
После загрузки core.json:
Привет, {handle}! Чем займемся сегодня?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ОСНОВНЫЕ РЕЖИМЫ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 📚 ОБУЧЕНИЕ - изучить новую тему или подтянуть слабую
2. 💻 ПРАКТИКА - решить задачу
3. 🔨 DRILL - набить код из библиотеки по памяти
4. 📊 CONTEST - проанализировать результаты контеста
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
СТАТИСТИКА И НАСТРОЙКИ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5. 📈 STATS - показать статистику прогресса
6. 📅 PLAN - показать план тренировок
7. 🔄 SYNC - синхронизировать с Codeforces
8. ⚙️ НАСТРОЙКИ - init/reset и прочее
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Выбери номер режима (1-8):
Обработка выбора:
choice = input().strip()
if choice == "1":
elif choice == "2":
elif choice == "3":
elif choice == "4":
elif choice == "5":
print("\nВернуться в меню? (да/нет)")
return_choice = input().strip().lower()
if return_choice in ["да", "yes", "y"]:
pass
elif choice == "6":
print("\nВернуться в меню? (да/нет)")
return_choice = input().strip().lower()
if return_choice in ["да", "yes", "y"]:
pass
elif choice == "7":
print("\nВернуться в меню? (да/нет)")
return_choice = input().strip().lower()
if return_choice in ["да", "yes", "y"]:
pass
elif choice == "8":
else:
print("Неверный выбор. Введи число от 1 до 8.")
Подменю настроек
Если пользователь выбрал пункт 8 в главном меню:
⚙️ НАСТРОЙКИ
1. 🔄 INIT - переинициализировать профиль (создать новый)
2. ❌ RESET - сбросить прогресс по теме
3. ← НАЗАД - вернуться в главное меню
Выбери действие (1-3):
Обработка выбора:
choice = input().strip()
if choice == "1":
print("⚠ Это создаст новый профиль! Старый будет перезаписан.")
print("Уверен? (да/нет)")
confirm = input().strip().lower()
if confirm in ["да", "yes", "y"]:
else:
print("Отменено.")
elif choice == "2":
print("Введи название темы для сброса:")
print("Например: binary_search, dp, graphs")
topic = input().strip()
if topic:
print("\nВернуться в меню настроек? (да/нет)")
return_choice = input().strip().lower()
if return_choice in ["да", "yes", "y"]:
pass
else:
pass
else:
print("Тема не указана. Возвращаюсь в меню.")
elif choice == "3" or choice.lower() in ["назад", "back"]:
else:
print("Неверный выбор. Введи 1, 2 или 3.")
Режим 1: Обучение
1.1. Выбор темы
Выбор темы опирается на profile (confidence, recent_summary из core.json), а НЕ на существование файлов в editorials/.
📚 Режим обучения
Твои слабые темы (по приоритету):
{for i, topic in enumerate(weak_topics[:5], 1):
confidence = topic_confidence[topic]
print(f"{i}. {topic} (confidence: {confidence}/10)")
}
Выбери номер темы (1-{len}) или введи название своей темы:
Пользователь выбирает тему → сохранить в current_session.topic
1.2. Проверка и подготовка материалов
Источник истины — profile (confidence, session_history в stats.json), а не файлы в editorials/.
Логика:
if [ -d "editorials/{topic}" ]:
has_materials = true
else:
has_materials = false
Если has_materials == false:
Материалов по теме "{topic}" пока нет.
Хочешь чтобы я создал структурированные заметки? (да/нет)
Это поможет:
- Карту знаний по теме
- Атомарные заметки с концепциями
- Примеры кода в библиотеке
Если "да" → запустить встроенную логику создания материалов (см. раздел "Встроенные команды" ниже)
1.3. Изучение материалов
Перед показом материалов: проверить есть ли предыдущие сессии по этой теме с weak_subtopics (из stats.json → session_history):
prev_weak = []
for session in reversed(stats.session_history):
if session["mode"] == "learning" and session["topic"] == topic:
prev_weak = session.get("weak_subtopics", [])
break
Если prev_weak не пустой:
Я помню, что в прошлый раз ты не до конца разобрался с:
⚠ {prev_weak[0]}, {prev_weak[1]}, ...
Давай начнем с этого!
Показать содержимое editorials/{topic}/index.md (если есть)
Прочитал? Вот ключевые концепции, которые важно понять:
{если prev_weak не пустой:
"Акцент на прошлый раз:"
для каждого subtopic из prev_weak → объяснить подробнее
"Остальные:"
показать остальные концепции кратко
}
{иначе:
извлечь из index.md секцию "Key Concepts" или первые 3 заметки
}
Если что-то непонятно - спрашивай! Могу объяснить подробнее любую часть.
Когда будешь готов, напиши "готов к quiz" и я проверю твоё понимание.
1.4. Quiz
Когда пользователь пишет "готов":
Отлично! Небольшой quiz для проверки понимания.
Буду задавать вопросы - отвечай как можешь, не бойся ошибаться.
{questions_count = min(5, len(concepts))}
Начинаем!
Генерация вопросов (динамически на основе материалов):
Стандартный набор вопросов:
- Концептуальный вопрос (subtopic: definition): "Что такое {ключевая концепция}?"
- Применение (subtopic: use_cases): "В каких задачах применяется {техника}?"
- Код (subtopic: implementation): Показать код из библиотеки, спросить про сложность или назначение
- Pattern recognition (subtopic: pattern_matching): Описать задачу, спросить какой подход применить
- Граничные случаи (subtopic: edge_cases): "Какие edge cases нужно учесть?"
Приоритизация по prev_weak:
Если есть prev_weak из предыдущей сессии — вопросы на эти subtopics ставятся первыми в порядке quiz. Например если prev_weak = ["edge_cases", "implementation"], то порядок: edge_cases → implementation → остальные.
Инициализировать перед quiz:
weak_subtopics = []
strong_subtopics = []
question_subtopic_map = {
"Conceptual": "definition",
"Application": "use_cases",
"Code": "implementation",
"Pattern recognition": "pattern_matching",
"Edge cases": "edge_cases"
}
Для каждого вопроса:
- Определить subtopic по типу вопроса из маппинга выше
- Ждать ответ пользователя
- Оценить правильность (не требовать точного совпадения, оценивать понимание)
- Если правильно → подтвердить и пояснить нюансы → добавить subtopic в strong_subtopics
- Если неправильно → дать наводящий вопрос → добавить subtopic в weak_subtopics
После quiz:
Quiz завершен!
Твой результат: {correct}/{total}
{if correct >= total * 0.8:
"Отлично! Ты хорошо понял тему."
confidence_increase = 2
}
{elif correct >= total * 0.6:
"Неплохо! Есть понимание, но нужна практика."
confidence_increase = 1
}
{else:
"Стоит перечитать материалы и попробовать еще раз."
confidence_increase = 0
}
1.5. Практическая задача
Теперь закрепим на практике!
Вот простая задача на тему "{topic}":
{выбрать задачу из CF с тегом topic и rating < user_rating - 200}
{показать условие через WebFetch}
Попробуй решить!
Далее работать в режиме диалога (как в режиме практики ниже).
1.6. Код из библиотеки
После решения задачи:
Отлично! Теперь посмотри на готовый шаблон в библиотеке:
{показать код из library/{lang}/{topic}/}
Этот код можно копировать в будущие задачи.
Ключевые моменты:
{объяснить 2-3 важных момента из кода}
Сохрани этот паттерн - он пригодится!
1.7. Обновление профиля
old_confidence = core.knowledge_assessment.topic_confidence[topic]
new_confidence = min(10, old_confidence + confidence_increase)
core.knowledge_assessment.topic_confidence[topic] = new_confidence
if new_confidence >= 6 and topic in core.knowledge_assessment.weak_topics:
core.knowledge_assessment.weak_topics.remove(topic)
summary_entry = {
"date": current_date_iso,
"mode": "learning",
"outcome": "completed",
"topics": [topic],
"confidence_delta": {topic: new_confidence - old_confidence}
}
core.recent_summary.append(summary_entry)
if len(core.recent_summary) > 10:
core.recent_summary = core.recent_summary[-10:]
core.last_updated = current_date_iso
save_core(core)
session_record = {
"date": current_date_iso,
"mode": "learning",
"topic": topic,
"quiz_score": f"{correct}/{total}",
"confidence_before": old_confidence,
"confidence_after": new_confidence,
"duration_minutes": elapsed_time,
"materials_created": not has_materials,
"weak_subtopics": weak_subtopics,
"strong_subtopics": strong_subtopics
}
stats.session_history.append(session_record)
stats.total_stats.total_sessions += 1
stats.total_stats.learning_sessions += 1
save_stats(stats)
Показать итог:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Обучающая сессия завершена!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Тема: {topic}
Quiz: {correct}/{total}
Confidence: {old_confidence} → {new_confidence}
{if strong_subtopics:
"✅ Усвоено: " + ", ".join(strong_subtopics)
}
{if weak_subtopics:
"⚠ Стоит повторить: " + ", ".join(weak_subtopics)
}
{if new_confidence >= 8:
"🎉 Тема освоена! Можно переходить к следующей."
}
{elif new_confidence >= 6:
"👍 Хорошее понимание! Закрепи практикой."
}
{else:
"📖 Стоит вернуться к теме через пару дней."
}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Хочешь еще одну сессию? Введи /coach
Режим 2: Практика
Claude в режиме практики — консультант, не инструктор. Не задаёт наводящие вопросы сам, не подтверждает и не опровергает наблюдения проактивно. Просто отвечает на вопросы пользователя, когда тот спрашивает. Не намекает на решение.
2.1. Выбор задачи
💻 Режим практики
У тебя есть конкретная задача или порекомендовать?
1. Ввести ссылку на задачу
2. Получить рекомендацию на основе слабых тем
3. Получить рекомендацию на основе плана тренировок
Выбери (1-3):
Вариант 1: Пользователь вводит ссылку
Введи ссылку на задачу (Codeforces, AtCoder, etc):
Получить ссылку → сохранить в current_session.problem_url
Вариант 2: Рекомендация по слабым темам
stats = load_stats()
weak_topic = sorted(
weak_topics,
key=lambda t: (
topic_confidence[t],
stats.topics_history.get(t, {}).get("last_practice") or "1970-01-01"
)
)[0]
print(f"Рекомендую тему '{weak_topic}' (твоя слабая сторона).")
print(f"Найди задачу на эту тему на Codeforces (рейтинг ~{max(800, user_rating)})")
print(f"И кинь мне ссылку!")
Вариант 3: По плану тренировок
focus_topic = training_plan.focus_areas[current_day % len(focus_areas)]
print(f"По плану сегодня тема '{focus_topic}'.")
print(f"Найди задачу на эту тему на Codeforces (рейтинг ~{user_rating})")
print(f"И кинь мне ссылку!")
2.2. Чтение условия задачи
Загружаю условие задачи...
problem_text = web_fetch(problem_url, "Extract problem statement")
Если WebFetch не работает (задача требует авторизации или URL недоступен):
⚠ Не могу загрузить условие по ссылке.
Возможные причины:
- Задача закрыта / требует авторизации
- Ошибка сети
Скопируй условие задачи сюда и я работаю с ним.
Показать краткий пересказ:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Задача: {problem_name}
Сложность: {rating}
Темы: {tags}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{краткое условие - 2-3 предложения}
Ограничения:
{время, память, ограничения на входные данные}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Полное условие: {problem_url}
Решай! Если есть вопросы — спрашивай.
2.3. Диалог (режим консультанта)
Инициализировать контекст сессии:
session_context = {
"problem_url": problem_url,
"problem_tags": tags,
"start_time": current_time,
"thoughts": [],
"breakthroughs": [],
"critical_questions": [],
"attempts": []
}
Модель диалога — консультант:
- Claude НЕ задаёт наводящие вопросы первым
- Claude НЕ подтверждает / НЕ опровергает наблюдения проактивно
- Если пользователь прямо спрашивает "Это верно?" или "Как мне X?" — отвечает честно и прямо
- Если пользователь спрашивает про сложность / корректность подхода — помогает проанализировать
- Если пользователь просит объяснить что-то из условия — объясняет
- Claude запоминает все наблюдения и идеи пользователя в session_context для итогового разбора
Сохранение прогресса в течение диалога:
if is_observation(user_message):
session_context.thoughts.append({
"text": user_message,
"timestamp": current_time
})
if is_breakthrough(user_message):
session_context.breakthroughs.append({
"insight": user_message,
"timestamp": current_time
})
2.4. После решения
Когда пользователь говорит "решил" / "accepted" / показывает AC:
🎉 Поздравляю с AC!
Давай кратко разберем решение:
Краткий разбор (3-5 минут):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Итог: ✅ Решено
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⏱ Время: {elapsed_minutes} минут
✅ Что получилось:
{перечислить ключевые наблюдения из session_context.thoughts}
💡 Ключевая идея:
> {главный breakthrough из session_context.breakthroughs}
🏷 Темы:
{problem_tags}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Спросить:
Хочешь создать детальный разбор в editorials? (да/нет)
Это поможет вспомнить решение потом.
Если "да" → использовать встроенную логику разбора (см. раздел "Встроенные команды")
2.5. Если не решил
Если пользователь пишет "сдаюсь" / "не могу":
Не проблема! Иногда задача оказывается слишком сложной.
Рекомендую: найти разбор этой задачи (editorial на CF или в интернете),
вставить его в solution/PROBLEM.md после "# Разбор" и запустить
обсуждение через claude.md (Режим 1 — объяснение готового разбора).
Это лучший способ разобрать задачу — ты увидишь где именно потерялся.
Сохранить как unsolved:
session_context.outcome = "unsolved"
session_context.reason = "gave_up"
2.6. Обновление профиля
outcome = "solved" if user_solved else "unsolved"
stats = load_stats()
for tag in problem_tags:
if tag not in stats.topics_history:
stats.topics_history[tag] = {
"attempts": 0,
"solved": 0,
"last_practice": None,
"avg_difficulty": 0
}
stats.topics_history[tag].attempts += 1
if outcome == "solved":
stats.topics_history[tag].solved += 1
stats.topics_history[tag].last_practice = current_date
old_avg = stats.topics_history[tag].avg_difficulty
count = stats.topics_history[tag].attempts
new_avg = (old_avg * (count - 1) + problem_rating) / count
stats.topics_history[tag].avg_difficulty = new_avg
if outcome == "solved":
for tag in problem_tags:
if tag in core.knowledge_assessment.topic_confidence:
current_conf = core.knowledge_assessment.topic_confidence[tag]
if problem_rating >= user_rating - 100:
new_conf = min(10, current_conf + 1)
core.knowledge_assessment.topic_confidence[tag] = new_conf
summary_entry = {
"date": current_date_iso,
"mode": "practice",
"outcome": outcome,
"topics": problem_tags,
"confidence_delta": {}
}
core.recent_summary.append(summary_entry)
if len(core.recent_summary) > 10:
core.recent_summary = core.recent_summary[-10:]
core.last_updated = current_date_iso
save_core(core)
session_record = {
"date": current_date_iso,
"mode": "practice",
"problem_url": problem_url,
"problem_rating": problem_rating,
"outcome": outcome,
"time_spent_minutes": elapsed_minutes,
"topics_practiced": problem_tags,
"key_insights": [b["insight"] for b in session_context.breakthroughs],
"attempts_count": len(session_context.attempts)
}
stats.session_history.append(session_record)
stats.total_stats.total_sessions += 1
stats.total_stats.practice_sessions += 1
if outcome == "solved":
stats.total_stats.total_solved += 1
if stats.total_stats.practice_sessions > 0:
stats.total_stats.success_rate = round(
stats.total_stats.total_solved / stats.total_stats.practice_sessions * 100, 1
)
save_stats(stats)
if detailed_log_requested:
save_session_log(f"coach/sessions/{current_date}_{problem_id}.md", session_context)
Показать итог:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Практическая сессия завершена!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Задача: {problem_name}
Результат: {outcome}
Время: {elapsed_minutes} мин
{if solved:
f"Темы: {problem_tags}"
f"Confidence обновлен: {показать изменения}"
}
Всего решено задач: {total_solved_count}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Хочешь решить еще одну? Введи /coach
Режим 3: Drill (набивашки)
Drill — тренировка мышечной памяти. Claude показывает метаданные алгоритма из library/, пользователь набивает код по памяти, Claude семантически сравнивает с эталоном и даёт обратную связь.
3.1. Выбор темы
Если вызван через /coach drill {topic}:
Берём файл library/{preferred_language}/{topic}.{ext} (где ext — cpp для C++, py для Python).
Если файл не найден:
⚠ Файл library/{preferred_language}/{topic}.{ext} не найден.
Доступные темы:
{список файлов в library/{preferred_language}/}
Введи имя темы:
Если вызван через /coach drill или через меню (пункт 3):
Автовыбор темы:
weak_topics = core.knowledge_assessment.weak_topics
lang = core.user_info.preferred_language
available = []
for topic in weak_topics:
files = glob(f"library/{lang}/{topic}.*")
if files:
available.append(topic)
stats = load_stats()
drill_history = stats.get("drill_history", [])
def last_drill_date(topic):
for entry in reversed(drill_history):
if entry["topic"] == topic:
return entry["date"]
return "1970-01-01"
if available:
selected = sorted(available, key=last_drill_date)[0]
else:
all_files = glob(f"library/{lang}/*.*")
print("В библиотеке нет файлов по твоим слабым темам.")
print("Доступные темы:")
for f in all_files:
print(f" - {basename(f)}")
print("Выбери тему:")
3.2. Парсинг файла library
Claude читает файл и извлекает три секции по маркерам:
| Секция | Маркеры | Использование |
|---|
| Metadata | Строки 1–4 (заголовочные комментарии // для cpp, # для py) | Имя, ключевые слова, сложность, описание |
| Эталон | НАЧАЛО КОПИРУЕМОГО БЛОКА … КОНЕЦ КОПИРУЕМОГО БЛОКА | Сравнение с ответом пользователя |
| Подсказка | РЕАЛИЗАЦИЯ С КОММЕНТАРИЯМИ … КОНЕЦ КОММЕНТИРОВАННОЙ ВЕРСИИ | Опциональный hint |
Парсинг metadata:
line1 → name
line2 → keywords
line3 → complexity
line4 → description
Парсинг эталона:
reference_code = extract_between("НАЧАЛО КОПИРУЕМОГО БЛОКА", "КОНЕЦ КОПИРУЕМОГО БЛОКА")
Парсинг подсказки:
hint_code = extract_between("РЕАЛИЗАЦИЯ С КОММЕНТАРИЯМИ", "КОНЕЦ КОММЕНТИРОВАННОЙ ВЕРСИИ")
Извлечение функций из эталона:
functions = extract_function_signatures(reference_code)
3.3. Отображение задания
Определить рабочий файл по языку:
if lang == "cpp":
solution_file = "solution/cpp/task.cpp"
else:
solution_file = "solution/py/main.py"
📚 Drill: {Имя алгоритма}
Сложность: {сложность}
Описание: {описание}
Функции для набивания:
- {function1}(args) → return_type
- {function2}(args) → return_type
...
Use cases:
- {примеры использования из metadata/описания}
Код НЕ показывается. Набивай в файл:
👉 {solution_file}
─────────────────────────────
Когда закончишь — напиши "готово", и я проверю.
3.4. Прогрессивность (подсказки)
Перед тем как пользователь начнёт набивать — проверяем stats.json → drill_history:
has_previous_drill = any(
entry["topic"] == topic for entry in drill_history
)
if not has_previous_drill:
print("Это твой первый drill по этой теме.")
print("Хочешь подсказку? (комментированная версия кода)")
else:
pass
3.5. Семантическое сравнение (после ввода кода пользователем)
Когда пользователь написал код в solution_file и сказал "готово":
- Прочитать файл
solution_file (solution/cpp/task.cpp или solution/py/main.py)
- Сравнить содержимое с
reference_code (эталон из library)
Claude сравнивает НЕ текстовым diff-ом, а по смыслу:
- Все ли функции из эталона написаны? — сопоставить список функций
- Верна ли логика каждой функции? — проверить алгоритм, условия, циклы
- Обработаны ли edge cases? — n=0, n=1, пустые контейнеры и т.д.
- Соответствует ли заявленной сложности? — O(√n), O(n log n) и т.д.
Формат результата:
═══════ Результат drill ═══════
✅ Верно:
- {что написано правильно}
⚠️ Пропущено:
- {что не написано, но есть в эталоне}
❌ Ошибки:
- {логические ошибки}
Точность: {N}/{M} функций ({percentage}%)
───────────────────────────────
📄 Эталон (КОПИРУЕМЫЙ БЛОК):
{полный код эталона из reference_code}
───────────────────────────────
Повторить эту тему? (да/нет) Или следующая тема?
3.6. Сохранение результата
Записываем в stats.json → drill_history:
drill_entry = {
"date": current_date_iso,
"topic": topic,
"language": lang,
"functions_drilled": functions_list,
"accuracy_score": correct / total,
"missed_parts": missed_list,
"used_hint": used_hint
}
stats.drill_history.append(drill_entry)
save_stats(stats)
Важно: Drill не обновляет confidence в core.json. Drill — тренировка мышечной памяти, не изучение темы. Confidence обновляется только через learning и practice.
3.7. Цикл
После показа результата — предложить:
Повторить эту тему? (да/нет) Или следующая тема?
- Если "да" / "повторить" → вернуться к шагу 3.3 (показать задание заново, без подсказки)
- Если "нет" / "следующая" → вернуться к шагу 3.1 (автовыбор новой темы)
- Если пользователь хочет выйти → завершить drill
Режим 4: Анализ контеста
Вызов: /coach contest
Цель: Структурированный анализ стратегии и распределения времени на контесте.
Подход: Аналогично работе с задачами - ссылка берётся из solution/CONTEST.md.
4.1. Валидация и подготовка
- Проверка профиля:
if [ ! -f coach/core.json ]; then
echo "❌ Профиль не найден. Создай профиль: /coach init"
exit
fi
- Извлечение handle:
handle=$(jq -r '.user_info.codeforces_handle' coach/core.json)
if [ "$handle" = "null" ] || [ -z "$handle" ]; then
echo "❌ Handle не указан в профиле. Переинициализируй: /coach init"
exit
fi
- Чтение CONTEST.md:
if not os.path.exists("solution/CONTEST.md"):
print("❌ Файл solution/CONTEST.md не найден.")
print(" Создайте его по шаблону из документации.")
exit
with open("solution/CONTEST.md") as f:
content = f.read()
contest_url = extract_section(content, "# Ссылка на контест").strip()
contest_type = extract_section(content, "# Тип контеста").strip().lower()
user_handle_section = extract_section(content, "## Ваш handle").strip()
team_handles_section = extract_section(content, "## Участники команды").strip()
if not contest_url or contest_url == "-":
print("❌ Вставь ссылку на контест в solution/CONTEST.md")
print(" Блок: # Ссылка на контест")
exit
if contest_type not in ["solo", "team"]:
print("❌ Тип контеста должен быть 'solo' или 'team'")
exit
if contest_type == "team":
team_handles = [user_handle_section]
for line in team_handles_section.split('\n'):
line = line.strip()
if line and line != "-" and not line.startswith("<!--"):
team_handles.append(line)
if len(team_handles) < 2:
print("❌ Для team контеста укажи хотя бы одного участника команды")
print(" Блок: ## Участники команды")
exit
else:
team_handles = [user_handle_section]
- Парсинг contest_id из ссылки:
import re
match = re.search(r'codeforces\.com/contest(?:Registration)?/(\d+)', contest_url)
if not match:
print("❌ Неверный формат ссылки на контест")
print(" Ожидается: https://codeforces.com/contest/{id}")
exit
contest_id = int(match.group(1))
- Фетч данных:
if contest_type == "solo":
python3 .claude/skills/competitive-coach/scripts/codeforces_api.py --contest "$user_handle_section" "$contest_id"
else:
python3 .claude/skills/competitive-coach/scripts/codeforces_api.py --team "$contest_id" "${team_handles[@]}"
Обработка ошибок:
- "Contest {id} not found" → "❌ Контест не существует. Проверь ссылку"
- "did not participate" → "❌ Ты не участвовал в этом контесте"
- "Network error" → "❌ Не могу подключиться к CF API. Попробуй позже"
4.2. Отображение результатов
Для solo режима:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Контест {contest_id}: {contest_name}
Результаты {handle}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Задача │ Попытки │ Verdict │ Время │ Теги
───────┼─────────┼─────────┼─────────┼─────────
A │ 1 │ OK │ 5 мин │ greedy
B │ 2 │ OK │ 15 мин │ dp
C │ 3 │ OK │ 30 мин │ graphs
D │ 1 │ WA │ — │ dp, trees
E │ 0 │ — │ — │ flows
F │ 0 │ — │ — │ geometry
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Итого: 3 решено, 1 попытка без AC, 2 не брали
Для team режима:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Контест {contest_id}: {contest_name}
Результаты команды: {", ".join(team_handles)}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Задача │ Решил │ Время │ Попыток (команда) │ Теги
───────┼──────────┼─────────┼───────────────────┼─────────
A │ tourist │ 5 мин │ 2 (T:1, P:1) │ greedy
B │ Petr │ 15 мин │ 3 (P:3) │ dp
C │ tourist │ 30 мин │ 5 (T:3, V:2) │ graphs
D │ — │ — │ 8 (T:3, P:5) │ dp, trees
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Итого: 3/6 решено командой
Суммарное время: 50 мин
Примечание для team режима:
- В колонке "Попыток (команда)" указать сумму попыток всех участников
- В скобках указать разбивку по участникам (первая буква handle)
- Колонка "Решил" содержит handle того, кто первым получил AC (или с лучшим временем)
4.3. Диалог: вопросы про нерешённые задачи
Solo режим:
Для каждой задачи с attempts > 0 и verdict != OK:
📝 {index} ({tags}) — почему не решил? Где застрял?
> [ждать ввод пользователя]
Для каждой задачи с attempts == 0:
📝 {index} ({tags}) — почему не решил?
> [ждать ввод пользователя]
Обработка пропусков:
- Пользователь нажимает Enter без текста → сохранить ""
- В анализе: если пусто → "Причина не указана"
Сохранить в: unsolved_reasons[index] = user_input
Team режим:
Для каждой задачи с attempts > 0 (любого участника) и verdict != OK:
📝 {index} ({tags}) — кто пытался решить? Где застряли?
> [ждать ввод пользователя]
Для каждой задачи с attempts == 0:
📝 {index} ({tags}) — почему не решили?
> [ждать ввод пользователя]
Сохранить в: unsolved_reasons[index] = user_input
4.4. Общий вопрос про стратегию
Solo режим:
📝 Как выбирал задачи? В каком порядке решал? Был ли план?
> [ждать ввод пользователя]
Сохранить в: strategy_notes = user_input
Team режим (дополнительные вопросы):
📝 Как распределяли задачи? Был ли план?
> [ждать ввод: strategy_notes]
📝 Были ли проблемы с коммуникацией? Как координировались?
> [ждать ввод: communication_notes]
📝 Кто какую роль выполнял? (решатель/кодер/дебаггер/...)
> [ждать ввод: roles_notes]
4.5. Генерация анализа (Solo режим)
4.5.1. Определение порядка решения
solved_sorted = sorted(
[(idx, data["time_min"]) for idx, data in submissions.items()
if data["verdict"] == "OK"],
key=lambda x: x[1]
)
solving_order = " → ".join([idx for idx, _ in solved_sorted])
4.5.2. Анализ стратегии
На основе strategy_notes и порядка решения:
if solving_order == "A → B → C → ...":
strategy_type = "Последовательная по индексу"
evaluation = "Стандартный подход для Div. 2"
else:
strategy_type = "Произвольный порядок"
evaluation = "Нестандартный порядок — проверь было ли это оптимально"
4.5.3. Распределение времени (правило "потери времени")
time_wasted_problems = []
total_solving_time = 0
total_wasted_time = 0
for index, data in submissions.items():
if data["verdict"] == "OK":
total_solving_time += data["time_min"]
elif data["attempts"] > 0:
estimated_time = 20
if estimated_time > 20:
time_wasted_problems.append(index)
total_wasted_time += estimated_time
Классификация задач:
< 15 мин → ✅ Быстро
15-20 мин → ✅ Нормально
20-30 мин → ⚠️ На грани
> 20 мин без AC → ❌ Потеря времени
4.5.4. Извлечение слабых тем
weak_tags = []
for index, data in submissions.items():
if data["verdict"] != "OK":
weak_tags.extend(data["tags"])
weak_topics_detected = list(set(weak_tags))
core = load_core()
weak_topics_profile = core["knowledge_assessment"]["weak_topics"]
confirmed_weaknesses = [t for t in weak_topics_detected if t in weak_topics_profile]
new_weaknesses = [t for t in weak_topics_detected if t not in weak_topics_profile]
4.5.5. Генерация файла анализа
Файл: editorials/{contest_id}_analysis.md (в корне editorials/, не в подпапке!)
Шаблон:
# Анализ контеста {contest_id}: {contest_name}
**Дата анализа:** {current_date}
**Результат:** {solved}/{total} задач решено
---
## Стратегия выбора задач
**Порядок решения:** {solving_order}
**Тип стратегии:** {strategy_type}
**Твои заметки:**
> {strategy_notes}
**Оценка:** {evaluation}
**Рекомендации:** {recommendations}
---
## Распределение времени
| Задача | Попытки | Verdict | Время (мин) | Оценка |
|--------|---------|---------|-------------|--------|
{для каждой задачи генерировать строку с emoji оценкой}
**Суммарное время на решённые:** {total_solving_time} мин
**Время на нерешённые попытки:** {total_wasted_time} мин (оценка)
**Правило "потери времени":** Задачи с >20 мин без AC
**Потери:** {time_wasted_problems}
---
## Паттерны ошибок
### Нерешённые задачи
{для каждой нерешённой задачи:}
#### {index} ({tags})
**Попытки:** {attempts}
**Verdict:** {verdict or "Не брался"}
**Причина:** {unsolved_reasons[index] or "Причина не указана"}
**Слабые темы по контесту:** {weak_topics_detected}
**Подтверждённые слабости (из профиля):** {confirmed_weaknesses}
**Новые слабости:** {new_weaknesses}
---
## Ключевые моменты
{динамическая генерация на основе данных:}
- ✅ Быстро решил {quick_problems}
- ⚠️ {borderline_problems} заняли {time} — рискованная граница
- ❌ {wasted_problems}: потерял время без прогресса
- 💡 Рекомендация: {recommendations}
---
## Связь с профилем
**Текущий уровень:** {rating}
**Целевой рейтинг:** {target_rating}
**Твои weak_topics:** {weak_topics_profile}
**В контесте не решил задачи с тегами:** {weak_topics_detected}
{анализ пересечений и рекомендации}
---
## Действия
- [ ] Повторить теорию: {weak_topics_detected} → /coach
- [ ] Решить похожие задачи на эти темы
- [ ] Довести до AC нерешённые: {unsolved_list}
4.5.6. Генерация анализа (Team режим)
Применяется только если contest_type == "team"
1. Вычисление метрик команды
contributions = {}
for handle, stats in aggregated["team_stats"].items():
contributions[handle] = {
"solved": stats["solved"],
"solved_count": len(stats["solved"]),
"attempts": stats["attempts"],
"contribution_pct": len(stats["solved"]) / len(aggregated["problems_solved"]) * 100 if aggregated["problems_solved"] else 0
}
duplicated_efforts = []
for index in contest_info["problems"]:
index_key = index["index"]
who_attempted = [h for h, subs in team_data.items()
if index_key in subs and subs[index_key]["attempts"] > 0]
if len(who_attempted) > 1:
duplicated_efforts.append({
"problem": index_key,
"who": who_attempted,
"total_attempts": sum(team_data[h][index_key]["attempts"] for h in who_attempted)
})
total_team_attempts = sum(stats["attempts"] for stats in aggregated["team_stats"].values())
synergy_score = len(aggregated["problems_solved"]) / total_team_attempts * 100 if total_team_attempts else 0
efficiency_score = (len(aggregated["problems_solved"]) / len(contest_info["problems"])) * (100 - len(duplicated_efforts) * 10)
weak_topics_team = []
for index in aggregated["problems_not_solved"]:
problem = next(p for p in contest_info["problems"] if p["index"] == index)
weak_topics_team.extend(problem["tags"])
weak_topics_team = list(set(weak_topics_team))
2. Генерация файла editorials/{contest_id}_team_analysis.md
Шаблон:
# Анализ командного контеста {contest_id}: {contest_name}
**Дата:** {date}
**Команда:** {", ".join(team_handles)}
**Результат:** {len(problems_solved)}/{len(all_problems)} решено
## Результаты команды
### Общая таблица
| Задача | Решил | Время | Попытки (команда) | Статус |
|--------|-------|-------|-------------------|--------|
{for каждой задачи:}
| {index} | {solutions_per_problem.get(index, "—")} | {time_per_problem.get(index, "—")} мин | {total_attempts} | {✅ if solved else ❌ Не решили} |
{endfor}
**Суммарное время на решённые:** {total_solve_time} мин
### Вклад участников
| Участник | Решено | Попыток | Вклад |
|----------|--------|---------|-------|
{for handle, contrib in contributions.items():}
| {handle} | {contrib["solved_count"]} | {contrib["attempts"]} | {contrib["contribution_pct"]:.1f}% |
{endfor}
**Детали:**
{for handle, contrib in contributions.items():}
- **{handle}:** Решил {", ".join(contrib["solved"])} ({комментарий})
{endfor}
## Эффективность команды
**Общая эффективность:** {efficiency_score:.0f}/100
**Синергия:** {synergy_score:.0f}/100
### Распределение задач
**Стратегия:** {strategy_notes}
**Оценка:**
{динамическая оценка на основе дублирования}
**Дублирование усилий:**
{if duplicated_efforts:}
{for dup in duplicated_efforts:}
- **{dup["problem"]}**: пытались {", ".join(dup["who"])} ({dup["total_attempts"]} попыток)
{endfor}
{else:}
- Дублирования не было ✅
{endif}
### Коммуникация
**Заметки:** {communication_notes}
**Оценка:**
{if len(duplicated_efforts) > 0:}
⚠️ Были случаи дублирования — улучшить координацию
{else:}
✅ Хорошая координация, дублирования не было
{endif}
### Роли
**Заметки:** {roles_notes}
## Слабые темы команды
**Темы из нерешённых задач:** {", ".join(weak_topics_team)}
### Связь с индивидуальными профилями
{for handle in team_handles:}
**{handle}:**
- Личные weak_topics: {core.get(handle, {}).get("weak_topics", [])}
- Пересечение: {intersection(weak_topics_team, personal_weak_topics)}
{endfor}
## Рекомендации
### По координации
{динамические рекомендации на основе duplicated_efforts}
### По подготовке
- Подтянуть темы команды: {weak_topics_team}
{for handle in team_handles:}
- {handle}: фокус на {его слабые темы}
{endfor}
### Действия
- [ ] Провести разбор нерешённых задач {", ".join(problems_not_solved)}
- [ ] Обсудить стратегию распределения для следующего контеста
{if duplicated_efforts:}
- [ ] Установить систему координации (кто что решает)
{endif}
4.6. Обновление stats.json
Solo режим:
stats = load_stats()
contest_record = {
"date": current_date_iso,
"contest_id": contest_id,
"contest_name": contest_name,
"problems_attempted": [idx for idx, d in submissions.items() if d["attempts"] > 0],
"problems_solved": [idx for idx, d in submissions.items() if d["verdict"] == "OK"],
"problems_not_attempted": [idx for idx, d in submissions.items() if d["attempts"] == 0],
"time_per_problem_min": {idx: d["time_min"] for idx, d in submissions.items()},
"attempts_per_problem": {idx: d["attempts"] for idx, d in submissions.items()},
"verdicts": {idx: d["verdict"] for idx, d in submissions.items()},
"strategy_notes": strategy_notes,
"unsolved_reasons": unsolved_reasons,
"weak_topics_detected": weak_topics_detected,
"time_wasted_problems": time_wasted_problems,
"analysis_file": f"editorials/{contest_id}_analysis.md"
}
if "contest_history" not in stats:
stats["contest_history"] = []
stats["contest_history"].append(contest_record)
save_stats(stats)
Team режим:
stats = load_stats()
team_record = {
"date": current_date_iso,
"contest_id": contest_id,
"contest_name": contest_name,
"team_members": team_handles,
"team_results": {
"problems_solved": aggregated["problems_solved"],
"problems_not_solved": aggregated["problems_not_solved"],
"total_problems": len(contest_info["problems"]),
"total_solve_time": aggregated["total_solve_time"],
"team_efficiency_score": efficiency_score,
"team_synergy_score": synergy_score
},
"individual_contributions": contributions,
"duplicated_efforts": duplicated_efforts,
"strategy_notes": strategy_notes,
"communication_notes": communication_notes,
"roles_notes": roles_notes,
"weak_topics_team": weak_topics_team,
"analysis_file": f"editorials/{contest_id}_team_analysis.md"
}
if "team_contest_history" not in stats:
stats["team_contest_history"] = []
stats["team_contest_history"].append(team_record)
save_stats(stats)
4.7. Итоговое сообщение
Solo режим:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Анализ контеста {contest_id} завершён!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Результат: {solved}/{total} решено
⏱️ Время: {total_solving_time} мин на решённые
⚠️ Потери: {time_wasted_problems}
📝 Анализ сохранён:
editorials/{contest_id}_analysis.md
💡 Рекомендации:
{if weak_topics_detected:}
• Подтянуть теорию: {weak_topics_detected}
Используй: /coach для практики
{if unsolved_problems:}
• Довести до AC: {unsolved_problems}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Хочешь попрактиковаться? Введи /coach
Team режим:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Анализ командного контеста {contest_id} завершён!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🤝 Команда: {", ".join(team_handles)}
📊 Результат: {solved}/{total} решено
⏱️ Время: {total_time} мин
⚡ Эффективность: {efficiency_score:.0f}/100
👥 Вклад:
{for handle, contrib in contributions.items():}
{handle}: {contrib["solved_count"]} задач ({contrib["contribution_pct"]:.1f}%)
{endfor}
📝 Анализ сохранён:
editorials/{contest_id}_team_analysis.md
💡 Рекомендации:
• Слабые темы команды: {weak_topics_team}
{if duplicated_efforts:}
• Улучшить координацию (было дублирование)
{endif}
• Каждому участнику: подтянуть свои weak_topics
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Дополнительные команды
/coach stats
Загрузить core.json и stats.json. Показать статистику прогресса:
📊 Твоя статистика
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
👤 Профиль
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Handle: {codeforces_handle}
Рейтинг: {stats.codeforces_sync.rating}
Цель: {target_rating} (осталось +{target - current})
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📈 Прогресс с момента инициализации
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Дней тренировок: {days_since_init}
Всего сессий: {stats.total_stats.total_sessions}
- Обучение: {stats.total_stats.learning_sessions}
- Практика: {stats.total_stats.practice_sessions}
Решено задач: {stats.total_stats.total_solved}
Успешность: {stats.total_stats.success_rate}%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💪 Confidence по темам
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{for topic, conf in sorted(topic_confidence.items(), key=lambda x: x[1], reverse=True):
bar = "█" * conf + "░" * (10 - conf)
print(f"{topic:20} {bar} {conf}/10")
}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🎯 Активность по темам
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{for topic in sorted(stats.topics_history, key=lambda t: stats.topics_history[t].attempts, reverse=True)[:10]:
history = stats.topics_history[topic]
success = history.solved / history.attempts * 100 if history.attempts > 0 else 0
print(f"{topic:20} {history.attempts:3} попыток | {history.solved:3} решено | {success:.0f}% успеха")
}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔨 Drill (набивашки)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Всего drill-сессий: {len(stats.drill_history)}
{# Группировка по уникальным темам
unique_topics = {}
for entry in stats.drill_history:
t = entry["topic"]
if t not in unique_topics:
unique_topics[t] = {"count": 0, "best_score": 0, "last_date": ""}
unique_topics[t]["count"] += 1
unique_topics[t]["best_score"] = max(unique_topics[t]["best_score"], entry["accuracy_score"])
unique_topics[t]["last_date"] = entry["date"]
for topic, data in unique_topics.items():
print(f" {topic}: {data['count']} раз, лучший: {data['best_score']*100:.0f}%, последний: {data['last_date']}")
}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
/coach sync
Синхронизировать данные с Codeforces. Обновляет stats.json:
🔄 Синхронизация с Codeforces...
{запустить codeforces_api.py снова}
{обновить codeforces_sync в stats.json}
{сравнить со старыми данными}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Обновлено!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Рейтинг: {old_rating} → {new_rating} ({change})
Решено задач: +{new_solved_count - old_solved_count}
Новые частые темы:
{показать изменения в frequent_tags}
Последняя синхронизация: {last_sync}
/coach plan
Показать детальный план тренировок:
📅 Твой план тренировок
{загрузить templates/training_plan.md}
{заполнить плейсхолдеры данными из core.json}
{показать план}
/coach reset {topic}
Сбросить прогресс по теме (если пользователь хочет переучить):
⚠ Сброс прогресса по теме "{topic}"
Это сбросит:
- Confidence (вернется к 5)
- Историю в topics_history
Уверен? (да/нет)
Если "да":
core.knowledge_assessment.topic_confidence[topic] = 5
if topic in core.knowledge_assessment.strong_topics:
core.knowledge_assessment.strong_topics.remove(topic)
if topic not in core.knowledge_assessment.weak_topics:
core.knowledge_assessment.weak_topics.append(topic)
save_core(core)
stats = load_stats()
stats.topics_history[topic] = {
"attempts": 0,
"solved": 0,
"last_practice": None,
"avg_difficulty": 0
}
save_stats(stats)
print(f"✅ Тема '{topic}' сброшена. Начни заново с /coach!")
Встроенные команды
/coach использует внутри себя логику существующих команд из проекта.
Создание материалов (логика из /topic_analysis)
Когда пользователь в режиме обучения выбирает тему и материалов нет:
- Предложить создать структурированные заметки
- Если согласен → запустить процесс создания:
Процесс создания:
Создаю структурированные заметки по теме "{topic}"...
Я создам:
- Карту знаний (граф концепций)
- Атомарные заметки (каждая концепция отдельно)
- Примеры кода в библиотеке
- Index файл со связями
Шаги:
-
Разбить тему на атомарные концепции (3-7 концепций)
-
Создать файлы:
editorials/{topic}/
├── index.md # Карта знаний
├── 01_concept1.md # Первая концепция
├── 02_concept2.md # Вторая концепция
└── ...
-
Для каждой концепции создать заметку в формате:
# {Название концепции}
## Определение
{краткое определение}
## Когда использовать
{паттерны и ситуации}
## Реализация
{базовый код или ссылка на library}
## Примеры задач
{2-3 задачи на концепцию}
## Связи
[[ссылки на другие концепции]]
-
Создать код в library в формате двойного блока из claude.md:
Формат файлов в library описан в claude.md раздел "Работа с библиотекой".
Каждый файл должен содержать:
// ============ НАЧАЛО КОПИРУЕМОГО БЛОКА ============
{чистый код БЕЗ комментариев — для копирования в решение}
// ============ КОНЕЦ КОПИРУЕМОГО БЛОКА ============
// ============ РЕАЛИЗАЦИЯ С КОММЕНТАРИЯМИ ============
{тот же код С подробными комментариями на русском — для изучения}
// ============ КОНЕЦ КОММЕНТИРОВАННОЙ ВЕРСИИ ============
// Примеры использования:
{пример вызова}
Структура:
library/{preferred_language}/{topic}/
├── {algo1}.cpp # Реализация алгоритма
└── README.md # Когда использовать
-
Создать index.md с картой:
# {Тема} — Карта знаний
## Обзор
{общее описание темы}
## Основные концепции
1. [[01_concept1]] - {краткое описание}
2. [[02_concept2]] - {краткое описание}
...
## Путь изучения
{рекомендуемый порядок изучения}
## Задачи для практики
{подборка задач от простых к сложным}
После создания:
✅ Материалы созданы!
📁 editorials/{topic}/
- {count} заметок
- 1 карта знаний
📚 library/{lang}/{topic}/
- {count} файлов с кодом
Теперь можешь изучать! Начнем с overview.
Разбор задач (логика из /solve_problem)
Когда пользователь в режиме практики решил задачу и хочет создать editorial:
- Предложить создать детальный разбор
- Если согласен → собрать информацию из session_context
Формат editorial (из task_editorial_example.md):
# {Название задачи}
## Ссылка
{problem_url}
## Теги
{tags}
## Что правильно понял
{мысли из session_context.thoughts}
## Пропущенный шаг
{что не сразу заметил, breakthroughs которые пришли позже}
## Решение
{краткое описание подхода}
### Код
```{lang}
{код если есть}
Сложность
- Время: O(...)
- Память: O(...)
Что подтянуть
{темы которые были сложными, ссылки на заметки в editorials/}
Связанные задачи
{похожие задачи если знаю}
Сохранить в:
editorials/{problem_id}_{problem_name}.md
После создания:
✅ Разбор сохранен!
📝 editorials/{problem_id}_{problem_name}.md
Можешь вернуться к нему потом через поиск или при изучении темы.
---
## Обработка ошибок
### CF API недоступен
⚠ Не могу подключиться к Codeforces API.
Возможные причины:
- Нет интернета
- CF API временно недоступен
- Неправильный handle
Продолжаем работать с локальными данными.
Попробуй синхронизировать позже через /coach sync
### Профиль поврежден
```bash
# Проверка при загрузке core.json
if ! jq empty coach/core.json 2>/dev/null; then
echo "⚠ Профиль поврежден!"
if [ -f coach/core.backup.json ]; then
echo "Найден backup. Восстановить? (да/нет)"
# если да: cp coach/core.backup.json coach/core.json
else
echo "Backup не найден. Создать новый профиль? (да/нет)"
# если да: запустить инициализацию
fi
fi
WebFetch не работает
⚠ Не могу загрузить условие задачи по ссылке.
Скопируй условие задачи сюда — я работаю с ним напрямую.
Тема не найдена в editorials
Материалов по теме "{topic}" пока нет.
Могу:
1. Создать материалы сейчас
2. Порекомендовать похожую тему из существующих
3. Перейти в режим практики (материалы не обязательны)
Что выбираешь? (1-3)
Технические детали
Структура профиля v2
coach/core.json — загружается при каждом /coach:
{
"version": "2.0",
"initialized_at": "2026-02-02T15:00:00Z",
"user_info": {
"codeforces_handle": "handle",
"preferred_language": "cpp",
"worked_with_claude": false
},
"knowledge_assessment": {
"topic_confidence": {
"binary_search": 8,
"two_pointers": 6,
"sorting": 7,
"dynamic_programming": 4,
"graphs_dfs_bfs": 5,
"greedy": 8,
"number_theory": 6,
"data_structures": 5,
"strings": 4,
"geometry": 3
},
"weak_topics": ["dp", "graphs"],
"strong_topics": ["binary_search", "greedy"]
},
"training_plan": {
"current_phase": "intermediate",
"target_rating": 1800,
"focus_areas": ["dp", "graphs", "data_structures"],
"weekly_goals": {
"problems_per_week": 10,
"learning_sessions_per_week": 2
},
"strategy": "focused"
},
"recent_summary": [
{
"date": "2026-02-03",
"mode": "practice",
"outcome": "solved",
"topics": ["dp"],
"confidence_delta": { "dp": 1 }
}
],
"last_updated": "2026-02-03T15:00:00Z"
}
coach/stats.json — загружается для /coach stats, /coach sync, и при обновлении session_history:
{
"version": "2.0",
"topics_history": {
"binary_search": {
"attempts": 12,
"solved": 10,
"last_practice": "2026-02-02",
"avg_difficulty": 1450
}
},
"total_stats": {
"total_sessions": 144,
"learning_sessions": 30,
"practice_sessions": 114,
"total_solved": 95,
"success_rate": 83.3,
"avg_solve_time_min": 35,
"current_streak_days": 12,
"longest_streak_days": 28
},
"monthly_summaries": [],
"codeforces_sync": {
"rating": 1650,
"solved_count": 312,
"tags_distribution": {},
"frequent_tags": ["implementation", "dp", "greedy"],
"rare_tags": ["geometry", "flows"],
"last_sync": "2026-02-01T10:00:00Z"
},
"session_history": [
{
"date": "2026-02-02T16:00:00Z",
"mode": "learning",
"topic": "binary_search",
"quiz_score": "4/5",
"confidence_before": 6,
"confidence_after": 8,
"duration_minutes": 45,
"weak_subtopics": [],
"strong_subtopics": ["definition", "use_cases"]
},
{
"date": "2026-02-02T17:00:00Z",
"mode": "practice",
"problem_url": "https://codeforces.com/problemset/problem/1444/A",
"problem_rating": 1500,
"outcome": "solved",
"time_spent_minutes": 40,
"topics_practiced": ["number_theory", "math"],
"key_insights": ["Remove prime factors separately"],
"attempts_count": 2
}
],
"drill_history": [
{
"date": "2026-02-05",
"topic": "prime_factorization",
"language": "cpp",
"functions_drilled": ["factorize", "get_prime_divisors", "count_divisors"],
"accuracy_score": 0.66,
"missed_parts": ["count_divisors — функция не была написана"],
"used_hint": false
}
]
}
coach/sessions/ — детальные логи (не загружаются автоматически, по запросу)
coach/archive/ — архив старых сессий (не загружается автоматически)
Backup стратегия
При каждом сохранении core.json:
cp coach/core.json coach/core.backup.json
echo "$new_profile" > coach/core.json
if ! jq empty coach/core.json 2>/dev/null; then
echo "Error: corrupted profile"
cp coach/core.backup.json coach/core.json
fi
Миграция из v1 в v2
Если существует старый coach/profile.json (v1):
python3 .claude/skills/competitive-coach/scripts/migrate_profile.py coach/profile.json coach/
Скрипт создаст core.json + stats.json автоматически.
Rate limiting для CF API
В codeforces_api.py уже реализован rate limiting (1 запрос в 2 секунды).
При вызове скрипта:
- Первый запрос (user.info): ~2 сек
- Второй запрос (user.status): +2 сек задержка
- Итого: ~4-5 сек на полный анализ
Кэширование:
- Результаты CF API кэшируются в stats.json (codeforces_sync)
- Синхронизация рекомендуется раз в день
- При ошибке API работаем с кэшированными данными
Примеры использования
Пример 1: Первая инициализация
User: /coach
Bot: Привет! Я твой персональный тренер...
Работал ли ты уже со мной?
User: нет
Bot: Понял! Я буду консультантом: отвечаю на вопросы когда ты спрашиваешь.
У тебя есть свои шаблоны алгоритмов где-либо?
User: да, есть несколько файлов
Bot: Скопируй их в library/cpp/ и library/py/.
Скажи когда готов — проверю.
User: скопировал
Bot: Проверил:
- C++: 5 файлов
- Python: 0 файлов
Укажи свой никнейм на Codeforces:
User: tourist
Bot: Твой профиль на Codeforces:
━━━━━━━━━━━━━━━━━━━━━━━━━
Рейтинг: 3821 (legendary grandmaster)
Решено задач: 2847
...
Bot: Начнем оценку знаний! Темы по одной.
Binary Search: Что такое бинарный поиск и когда он применяется?
User: Ну это поиск элемента в отсортированном массиве за O(log n)
Bot: Хорошо. А когда применять поиск по ответу?
User: Когда есть монотонная функция и нужно найти оптимальное значение, например минимальное время
Bot: Понял. Последний вопрос: как обработать ситуацию когда нужна первая позиция элемента среди дубликатов?
User: left = mid + 1 когда arr[mid] <= target, иначе right = mid
Bot: Binary Search: 9/10
Two Pointers: Что такие два указателя и когда применяются?
User: не знаю особо
Bot: Two Pointers: 1/10. Переходим дальше.
... (оценка по всем темам)
Bot: ✅ Профиль создан!
Фаза: expert
Цель: 4000 (сейчас 3821)
...
Пример 2: Режим обучения
User: /coach
Bot: Привет, tourist! Чем займемся сегодня?
1. 📚 ОБУЧЕНИЕ
2. 💻 ПРАКТИКА
User: 1
Bot: 📚 Режим обучения
Твои слабые темы:
1. two_pointers (confidence: 1/10)
2. geometry (confidence: 4/10)
Выбери номер темы:
User: 2
Bot: Материалов по теме "geometry" пока нет.
Хочешь чтобы я создал заметки? (да/нет)
User: да
Bot: Создаю структурированные заметки по теме "geometry"...
✅ Материалы созданы!
📁 editorials/geometry/ — 4 заметки
📚 library/cpp/geometry/ — 2 файла с кодом
Давай начнем с overview:
{показывает index.md}
... (пользователь читает)
User: готов к quiz
Bot: Отлично! Небольшой quiz.
Q1: Как проверить пересекаются ли два отрезка?
User: Использовать векторное произведение
Bot: Верно! Объясни подробнее как именно...
... (quiz продолжается)
Bot: Quiz завершен! Твой результат: 4/5
Confidence: 4 → 6
Пример 3: Режим практики
User: /coach
Bot: Привет! Чем займемся сегодня?
1. 📚 ОБУЧЕНИЕ
2. 💻 ПРАКТИКА
User: 2
Bot: 💻 Режим практики
1. Ввести ссылку
2. Рекомендация по слабым темам
3. Рекомендация по плану
User: 2
Bot: Рекомендую тему 'two_pointers' (твоя слабая сторона).
Найди задачу на эту тему на CF (рейтинг ~1500) и кинь ссылку!
User: https://codeforces.com/problemset/problem/1234/C
Bot: Загружаю условие...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Задача: Two Sum
Сложность: 1400
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{краткое условие}
Решай! Если есть вопросы — спрашивай.
User: Нужно найти количество различных символов на подотрезке
Bot: (ничего не говорит проактивно — ждёт вопросов)
User: Правильно ли я понимаю что можно использовать массив из 26 счетчиков?
Bot: Да, массив из 26 счетчиков — верное наблюдение. Какую сложность это даст на один запрос?
User: O(26) = O(1)
Bot: Верно.
... (пользователь продолжает, Claude отвечает только на вопросы)
User: Accepted!
Bot: 🎉 Поздравляю с AC!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Итог: ✅ Решено
⏱ Время: 35 минут
💡 Ключевая идея: ...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Хочешь создать детальный разбор? (да/нет)
User: да
Bot: ✅ Разбор сохранен!
📝 editorials/1234C_two_sum.md
Пример 4: Drill (набивашки)
User: /coach drill prime_factorization
Bot: 📚 Drill: Prime Factorization (Разложение на простые множители)
Сложность: O(√n)
Описание: Находит разложение числа n на простые множители и их степени
Функции для набивания:
- factorize(n) → map<prime, power>
- get_prime_divisors(n) → vector<prime>
- count_divisors(n) → количество делителей
Use cases:
- Факторизация для числовой теории
- Подсчёт количества делителей через формулу (a₁+1)×(a₂+1)×...
- Нахождение простых делителей числа
Это твой первый drill по этой теме.
Хочешь подсказку? (комментированная версия кода)
User: нет
Bot: Код НЕ показывается. Набивай в файл:
👉 solution/cpp/task.cpp
─────────────────────────────
Когда закончишь — напиши "готово", и я проверю.
User: готово
Bot: (читает solution/cpp/task.cpp)
Bot: ═══════ Результат drill ═══════
✅ Верно:
- factorize: логика перебора делителей от 2 до √n — корректна
- factorize: обработка остатка n > 1 — есть
- get_prime_divisors: корректно убирает все степени делителя внутри цикла
⚠️ Пропущено:
- count_divisors(n) — функция не была написана
Эта функция входит в КОПИРУЕМЫЙ БЛОК
❌ Ошибок не найдено
Точность: 2/3 функций (66%)
───────────────────────────────
📄 Эталон (КОПИРУЕМЫЙ БЛОК):
{полный код эталона}
───────────────────────────────
Повторить эту тему? (да/нет) Или следующая тема?
User: нет
Bot: Хочешь выбрать другую тему или завершить?
Введи /coach drill для следующей темы или /coach для главного меню.
Заметки по реализации
-
Все команды через один skill: /coach — единая точка входа, логика различается по аргументу.
-
Профиль v2 — два файла: core.json загружается всегда (минимум данных), stats.json загружается только когда нужна полная статистика или session_history.
-
Graceful degradation: Если CF API недоступен — работаем без него. Если материалов нет — создаем или работаем без них. Если WebFetch не работает — просим скопировать условие.
-
Консультант, не инструктор: В режиме практики Claude НЕ задаёт наводящие вопросы первым и НЕ подтверждает наблюдения проактивно. Отвечает только когда пользователь спрашивает.
-
Обновление confidence постепенное: +1-2 за сессию, не больше. Нужно время чтобы тема стала "strong".
-
recent_summary ограничен 10 записями: Полная история в stats.json. core.json не растет со временем.
-
Backup при каждом сохранении core.json: Профиль — критичные данные, всегда создавать backup.
-
Editorials — удобство, не источник истины: Наличие файлов в editorials/ не означает что тема "изучена". Источник истины — confidence и session_history в profile.
-
Миграция из v1: Скрипт scripts/migrate_profile.py конвертирует старый profile.json в core.json + stats.json.
-
Интеграция существующих команд: Не дублировать логику /topic_analysis и /solve_problem, использовать их внутри /coach.
-
Drill не влияет на confidence: Drill — тренировка мышечной памяти, не изучение темы. Confidence обновляется только через learning и practice.