Задачи и «конституция» проекта
Как разбить технический план на маленькие проверяемые задачи с порядком, зависимостями и параллельными волнами, связать каждую задачу с требованиями, задать определение готовности и «конституцию» проекта — неизменные принципы для людей и агентов; чек-листы качества спецификации и сколько markdown действительно нужно.
Зачем это нужно
Заголовок раздела «Зачем это нужно»У нас есть спецификация записи на поток (урок SDD.2.1) и технический план (урок SDD.2.2). Можно сказать агенту «реализуй план» — и через полчаса получить дифф на полторы тысячи строк: модель, сервис, хранилище и тесты разом. Прочитать его внимательно невозможно, тесты написаны после кода и подогнаны под него, а когда что-то сломается, непонятно, на каком шаге агент свернул не туда. К тому же за полчаса работы контекст агента заполнился, и к концу он помнит план хуже, чем в начале.
Лекарство — разбиение на задачиразбиение на задачиEN task breakdownПревращение плана в список маленьких проверяемых задач с порядком и зависимостями. Хорошая задача — один небольшой дифф, понятный критерий готовности и ссылка на требования. В Spec Kit — файл tasks.md.: маленькие шаги, каждый из которых можно проверить отдельно, с понятным порядком и ссылками на требования. Одна задача — один небольшой дифф, один прогон тестов, один коммит. Если агент ошибся, вы откатываете одну задачу, а не день работы.
Вторая половина урока — о правилах, которые действуют для всех задач и всех фич: тесты сначала, имена из глоссария, время через параметр, никаких новых зависимостей без решения. Писать их в каждом промпте утомительно, а забыть легко. Spec Kit собирает такие правила в конституцию проектаконституция проектаEN project constitutionТермин Spec Kit: файл с неизменными принципами проекта (в Spec Kit — .specify/memory/constitution.md), по которым проверяются план, задачи и код: тесты сначала, простота, стандарты кода, запреты. Меняется редко, через явную поправку с номером версии. — файл, по которому проверяются план, задачи и код. В конце разберём чек-листы качества спецификации и честно поговорим о том, сколько markdown на самом деле нужно, чтобы не превратить SDD в бюрократию.
Разбиение плана на задачи
Заголовок раздела «Разбиение плана на задачи»Какой должна быть задача
Заголовок раздела «Какой должна быть задача»| Свойство | Плохо | Хорошо |
|---|---|---|
| Размер | «Реализовать Набор» | «Агрегат Cohort: запись на поток и вместимость» — дифф на 100–200 строк, читается за 10–15 минут |
| Проверяемость | «Улучшить отмену» | «Готово, когда: тесты test_sc_006_… и test_sc_007_… зелёные» |
| Связь с требованиями | задача без ссылок | «Требования: REQ-006, REQ-007, REQ-008» |
| Зависимости | «после того, как будет готова модель» | «Зависит от: T-002, T-007» |
| Срез | «Сначала все модели, потом все сервисы» | одна история или одно правило через все слои: модель, сервис, тест |
| Однозначность | «Сделать как в плане» | конкретный класс, метод и файл: «Cohort.cancel в school/enrollment/model.py» |
Размер — главное: дифф задачи вы готовы прочитать целиком, а не пролистать. Большую задачу режут, задачу на три строки объединяют с соседней — на каждую уходит время на запуск, проверку и коммит.
Последние две строки таблицы связаны. Вертикальный срезвертикальный срезEN vertical sliceЗадача или этап, который проходит через все слои (модель, сервис, интерфейс, тесты) ради одной пользовательской истории и даёт работающий результат. Противоположность — горизонтальные этапы «сначала вся база, потом весь API». — задача, которая проводит одно правило через все слои и даёт работающий результат. Горизонтальные этапы («сначала вся модель, потом весь сервис») выглядят аккуратно, но первую работающую функцию вы увидите только в конце, и ошибку в понимании требования — тоже. Шаблон задач Spec Kit по той же причине группирует задачи по пользовательским историям: каждая история — свой этап, который можно реализовать, проверить и показать независимо от остальных.
Формат задачи в курсе
Заголовок раздела «Формат задачи в курсе»В курсе задачи пишут в tasks.md так — этот формат читает браузерное задание ниже и автопроверка домашней работы:
## T-007: Отмена неоплаченной записи через 48 часов и событие EnrollmentCancelledТребования: REQ-005, REQ-008Зависит от: T-004Готово, когда: тест test_sc_005_unpaid_enrollment_cancelled_after_48_hours зелёныйЧетыре поля, и у каждого своя работа. ID T-007 нужен, чтобы ссылаться на задачу из других задач, из коммитов и из
разговора с агентом. «Требования» — ссылки на REQ-… из спецификации. «Зависит от» — ID задач, которые должны быть
готовы раньше (—, если таких нет). «Готово, когда» — проверяемый критерий именно этой задачи.
В Spec Kit (шаблон templates/tasks-template.md на октябрь 2026) формат другой, но идеи те же. Две строки из
примера в шаблоне, как есть (в квадратных скобках — места для подстановки):
- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013)Там задача — пункт чек-листа: номер по порядку выполнения, пометка [P] («можно параллельно: другие файлы, нет
зависимостей от незавершённых задач»), метка истории [US1] и точный путь к файлу. Задачи разложены по фазам:
подготовка проекта (Setup), общий фундамент, который блокирует все истории (Foundational), по фазе на каждую историю в
порядке приоритета и финальная «полировка». Задачи на тесты шаблон генерирует, только если тесты явно запрошены в
спецификации или выбран подход TDD, — поэтому принцип «тесты сначала» стоит записать в конституцию (о ней ниже).
И ещё одно расхождение, о котором предупреждал урок SDD.2.1: в Spec Kit SC-001 — критерий успеха (Success
Criteria), а в нашем курсе — сценарий. Полная таблица соответствий формата курса, Spec Kit и OpenSpec (требования,
сценарии, истории, маркеры неясности) — в разделе «Формат курса и шаблоны Spec Kit и OpenSpec»
урока SDD.3.1.
Мы вместо пометки [P] записываем зависимости явно и вычисляем параллельность. Пометку легко забыть обновить,
когда меняется задача, а граф зависимостей проверяется программой.
Порядок, зависимости и параллельные волны
Заголовок раздела «Порядок, зависимости и параллельные волны»Граф зависимостей
Заголовок раздела «Граф зависимостей»Вот задачи для спецификации записи на поток из урока SDD.2.1. Стрелка ведёт от задачи к той, что от неё зависит.
flowchart LR T1["T-001<br/>приёмочные тесты<br/>(красные)"] --> T4 T3["T-003<br/>статусы и<br/>исключения"] --> T4["T-004<br/>Cohort: запись<br/>и вместимость"] T2["T-002<br/>Money"] --> T8 T4 --> T5["T-005<br/>повторная<br/>запись"] T4 --> T6["T-006<br/>закрытие<br/>при старте"] T4 --> T7["T-007<br/>отмена через<br/>48 часов"] T7 --> T8["T-008<br/>отмена студентом<br/>и возврат"] T5 --> T9["T-009<br/>README пакета"] T6 --> T9 T8 --> T9
Это граф зависимостей задачграф зависимостей задачEN task dependency graphОриентированный граф: задачи — вершины, «зависит от» — рёбра. Порядок выполнения даёт топологическая сортировка; цикл в графе означает, что план невыполним и его надо перерезать.: задачи — вершины, «зависит от» — рёбра. Из него отвечают на три вопроса: в каком порядке делать, что можно делать одновременно и нет ли в плане невозможного.
Порядок выполнения: топологическая сортировка
Заголовок раздела «Порядок выполнения: топологическая сортировка»Порядок, в котором каждая задача идёт после всех своих зависимостей, называется топологической сортировкойтопологическая сортировкаEN topological sortПорядок узлов ориентированного графа без циклов, в котором каждый узел идёт после всех, от кого зависит. backward() обходит его в обратную сторону.. Вы встречали её, если проходили обратное распространение ошибки в micrograd (урок 3.2.2) или алгоритмическую секцию собеседования (урок 11.3.5); здесь она нужна в самом простом виде — алгоритм Кана (Kahn):
- Задача готова, если все её зависимости уже выполнены. В начале готовы задачи без зависимостей.
- Из готовых берём одну — в курсе ту, у которой меньше ID, чтобы порядок был однозначным, — и добавляем в порядок.
- Вычёркиваем её из зависимостей остальных: кто-то из них мог стать готовым. Повторяем с шага 2.
Пройдём по графу выше:
| Шаг | Готовы | Берём | Что открылось |
|---|---|---|---|
| 1 | T-001, T-002, T-003 | T-001 | — |
| 2 | T-002, T-003 | T-002 | — (T-008 ждёт ещё T-007) |
| 3 | T-003 | T-003 | T-004 |
| 4 | T-004 | T-004 | T-005, T-006, T-007 |
| 5–6 | T-005, T-006, T-007 | T-005, затем T-006 | — |
| 7 | T-007 | T-007 | T-008 |
| 8 | T-008 | T-008 | T-009 |
| 9 | T-009 | T-009 | — |
Если задачи остались, а готовых нет, — в графе цикл: T-001 ждёт T-003, T-003 ждёт T-002, T-002 ждёт T-001. Такой план невыполним, и это ошибка плана, а не повод «начать с какой-нибудь». Обычно цикл значит, что две задачи на самом деле одна или что у них есть общая часть, которую надо вынести в отдельную задачу.
Параллельные волны
Заголовок раздела «Параллельные волны»Порядок говорит, что за чем, но не что одновременно. Для этого задачи раскладывают на параллельные волныпараллельная волна задачEN parallel group, waveГруппа задач, которые не зависят друг от друга и все зависимости которых уже выполнены, — их можно отдать нескольким людям или агентам одновременно. В Spec Kit такие задачи помечают [P].: волна задачи равна 1, если у неё нет зависимостей, иначе — 1 плюс наибольшая волна её зависимостей. Задачи одной волны друг от друга не зависят:
| Волна | Задачи | Почему здесь |
|---|---|---|
| 1 | T-001, T-002, T-003 | зависимостей нет |
| 2 | T-004 | ждёт T-001 и T-003 (волна 1) |
| 3 | T-005, T-006, T-007 | все ждут только T-004 |
| 4 | T-008 | ждёт T-007 (волна 3) и T-002 (волна 1) |
| 5 | T-009 | ждёт T-008 (волна 4) |
Пять волн вместо девяти шагов — столько «тактов» займёт работа, если исполнителей хватает. Длина самой длинной цепочки (T-001 → T-004 → T-007 → T-008 → T-009) — предел ускорения: сколько ни добавляй агентов, быстрее пяти волн не выйдет.
Независимость в графе — необходимое, но не достаточное условие параллельности. Spec Kit ставит [P] только задачам,
которые меняют разные файлы. T-005 и T-006 обе правят Cohort в model.py: два агента, работающие одновременно,
получат конфликт слияния. В таком случае волну делают последовательно или заранее делят код по файлам.
Трассируемость: от требования до коммита
Заголовок раздела «Трассируемость: от требования до коммита»ТрассируемостьтрассируемостьEN traceabilityВозможность пройти по ссылкам от требования к сценарию, задаче, тесту и коду и обратно. Держится на идентификаторах (REQ-001, SC-001, T-001) и позволяет автоматически найти требования без задач и задачи без требований. — возможность пройти по ссылкам от требования к коду и обратно. В курсе цепочка держится на идентификаторах:
flowchart LR R["REQ-006<br/>требование"] --> S["SC-006<br/>сценарий"] S --> X["test_sc_006_…<br/>приёмочный тест"] R --> T["T-008<br/>задача"] T --> C["коммит<br/>«T-008: …»"] X -.->|"проверяет"| C
Ссылки дают две проверки, которые делает не человек, а скрипт:
- требование без задачи — его забыли реализовать. Частая жертва — нефункциональные требования: «отвечать не дольше 500 мс» (REQ-010) легко пропустить, потому что оно не похоже на «фичу»;
- задача без требования — либо забыли записать требование, либо задача лишняя, либо это не задача, а пункт определения готовности. «Обновить README» — как раз третий случай.
В Spec Kit эту работу делает команда анализа согласованности: только на чтение, без правок, она сверяет spec.md,
plan.md и tasks.md и среди прочего ищет требования без задач и задачи без требований, расхождение терминов в
разных файлах, размытые прилагательные вроде «быстрый» и «надёжный» без измеримых критериев. Найденную проблему
исправляют в том документе, где она возникла, — в спецификации, плане или задачах — и запускают анализ снова.
Определение готовности
Заголовок раздела «Определение готовности»У каждой задачи в нашем формате есть «Готово, когда» — свой критерий. Но есть условия, общие для всех задач: тесты зелёные, линтер чист, глоссарий не нарушен. Повторять их в каждой задаче незачем — для этого есть определение готовностиопределение готовности (DoD)EN Definition of DoneОбщий для всех задач список условий, без которых работа не считается сделанной: тесты зелёные, линтер чист, документация и глоссарий обновлены, коммит ссылается на задачу. Понятие из Scrum; отличается от критериев приёмки, которые у каждого требования свои. (Definition of Done, DoD). Понятие пришло из Scrum: в руководстве по скраму это общее для команды описание того, каким должен быть результат, чтобы считаться готовым.
| Критерии приёмки | Определение готовности | |
|---|---|---|
| Чьё | у каждого требования свои | одно на все задачи проекта |
| Форма | сценарии «Дано / Когда / Тогда» | чек-лист |
| Пример | SC-006: отмена ровно за 7 суток — полный возврат | «pytest -q зелёный целиком» |
| Где живёт | spec.md | конституция или файл памяти проекта |
Пример DoD для онлайн-школы:
## Определение готовности задачи- Тесты задачи зелёные, и `pytest -q` зелёный целиком.- Имена — только из глоссария; новых синонимов нет.- Если поведение изменилось — spec.md и glossary.md обновлены в том же коммите.- В коде нет TODO, отладочных print и закомментированного кода.- Сообщение коммита начинается с ID задачи: «T-008: отмена студентом и возврат».Для агента DoD особенно важен: без него «готово» значит «код написан». Агент охотно объявит задачу выполненной, не запустив тестов, или «починит» падающий тест, ослабив проверку. Явный DoD — плюс хук, который сам запускает тесты после каждого шага, — закрывает обе дыры.
«Конституция» проекта
Заголовок раздела ««Конституция» проекта»Что это и откуда термин
Заголовок раздела «Что это и откуда термин»Слово «конституция» в этом смысле пришло из Spec Kit. Манифест проекта называет её набором неизменных принципов, которые определяют, как спецификации превращаются в код. По файлам репозитория на октябрь 2026:
- конституция лежит в
.specify/memory/constitution.mdи создаётся командойconstitution— один раз на проект, а не на каждую фичу (README: «конституция — один раз на проект; specify → plan → tasks → implement → converge — на каждую фичу»); - шаблон
constitution-template.md— это основные принципы (пять слотов под названия и описания), два раздела на выбор проекта (например, дополнительные ограничения и порядок разработки), раздел управления (как вносить поправки) и строка «версия, дата принятия, дата последней поправки»; - версия растёт по семантическому версионированию: удаление или переопределение принципа — старшая цифра, новый принцип — средняя, уточнение формулировки — младшая;
- команда конституции требует, чтобы принципы были декларативными и проверяемыми, без расплывчатых формулировок.
Как конституция работает
Заголовок раздела «Как конституция работает»Конституция — не плакат на стене, её проверяют. В шаблоне плана Spec Kit есть раздел «Constitution Check» с пометкой «контрольная точка: пройти до исследования и перепроверить после проектирования». Это контрольная точка качестваконтрольная точка качества (gate)EN quality gateПроверка, которую нужно пройти, прежде чем двигаться дальше: например, «Constitution Check» в плане Spec Kit до исследования и после проектирования. Нарушение либо исправляют, либо явно обосновывают.: план, который нарушает принцип, либо исправляют, либо обосновывают нарушение в разделе «Complexity Tracking» — что нарушено, зачем и почему простой вариант не подошёл. Команда анализа считает конфликт с конституцией критичной проблемой: менять в таком случае нужно спецификацию, план или задачи, а если неверен сам принцип — конституцию, отдельной явной поправкой.
Манифест Spec Kit приводит в пример девять статей: библиотека прежде приложения, интерфейс командной строки у каждой библиотеки, тесты прежде кода («не обсуждается»), три статьи на усмотрение проекта, простота (не больше трёх проектов для начала, ничего «на будущее»), отказ от лишних абстракций (пользоваться фреймворком напрямую) и интеграционные тесты на реальных зависимостях. Это пример, а не догма: шаблон оставляет принципы проекту.
Конституция онлайн-школы
Заголовок раздела «Конституция онлайн-школы»# Конституция проекта «Онлайн-школа»**Версия:** 1.0.0 · **Принята:** 2026-10-06 · **Последняя поправка:** 2026-10-06
## Принципы
### 1. Тесты сначалаКаждый сценарий спецификации — приёмочный тест с ID в имени (test_sc_006_…). Тест пишут до кодаи убеждаются, что он падает.
### 2. Единый языкИмена классов, методов, событий и тестов — только из глоссария. Новое понятие сначала вносят в глоссарий.
### 3. Время приходит снаружиДоменная модель не вызывает datetime.now(): текущий момент — параметр now.
### 4. Деньги — только MoneyСуммы — целое число копеек и валюта. Тип float для денег запрещён.
### 5. ПростотаТолько стандартная библиотека и pytest. Новая зависимость или слой — только с ADR.
## УправлениеПоправка — отдельным изменением с обоснованием; версия растёт по семантическому версионированию.Каждый принцип можно проверить: тестом, поиском по коду (datetime.now( в school/enrollment/), ревью зависимостей.
С принципом «пишем чистый код» агент согласится — и ничего не изменит.
Что не писать в конституцию
Заголовок раздела «Что не писать в конституцию»- Детали фичи. «Возврат — за 7 суток» — правило Набора, место ему в спецификации и глоссарии.
- То, что часто меняется. Версии библиотек, адреса серверов, команды сборки — в план или файл памяти проекта.
- Пожелания без проверки. «Код должен быть понятным» — не принцип, а надежда.
- Слишком много. Пять–семь принципов агент держит в голове, тридцать — нет; длинный список превращается в фон.
Конституция и файл памяти проекта (CLAUDE.md, AGENTS.md — урок SDD.1.2)
дополняют друг друга. Конституция — редкие и важные «что нельзя и что обязательно». Файл памяти — оперативное: как
запускать тесты, где лежат спецификации, и ссылка на конституцию. В OpenSpec похожую роль играет openspec/config.yaml:
поле context (стек, соглашения) и правила rules для каждого вида документов. В Kiro, по описанию в его
репозитории, соглашения проекта задают файлы «steering». Названия разные — идея одна: правила, общие для всех задач,
записаны один раз и попадают в контекст агента сами.
Чек-листы качества спецификации
Заголовок раздела «Чек-листы качества спецификации»Код проверяют тесты. А кто проверяет спецификацию? Spec Kit отвечает на этот вопрос чек-листами спецификациичек-лист спецификацииEN specification checklistСписок вопросов к качеству самих требований, а не к работе кода: полны ли они, однозначны, измеримы, согласованы. В документации Spec Kit их называют «юнит-тестами для ваших требований» (unit tests for your requirements); часть пунктов можно проверять скриптом.: справочник команд называет их «юнит-тестами для ваших требований» (unit tests for your requirements), а текст самой команды — ещё короче, «юнит-тестами для английского». Смысл один: если спецификация — это код, написанный на естественном языке, то чек-лист — его набор тестов. Важно, что они проверяют: качество требований, а не работу программы.
| Неправильно (проверяет реализацию) | Правильно (проверяет требования) |
|---|---|
| Проверить, что отмена возвращает деньги | Указано ли, какая сумма возвращается при отмене ровно на границе 7 суток? |
| Убедиться, что запись работает | Определено ли поведение, если студент записывается в момент старта потока? |
| Протестировать лист ожидания | Указано ли, сколько времени у студента на ответ и что происходит после срока? |
Команда спецификации в Spec Kit сама создаёт встроенный чек-лист checklists/requirements.md с группами «качество
содержания» (нет деталей реализации, написано для людей без технического образования), «полнота требований» (не осталось
маркеров неясности, требования проверяемы, критерии успеха измеримы, границы очерчены) и «готовность фичи». Отдельная
команда чек-листа строит дополнительные списки по запросу; отмечает пункты [x] проверяющий человек, а реализация
только читает их состояние как контрольную точку.
Чек-лист для пакета спецификаций в формате курса:
- У каждого требования есть ID
REQ-…, и одно требование — одно обязательство. - В требованиях нет слов из стоп-списка урока SDD.2.1.
- У каждого требования есть сценарий «Дано / Когда / Тогда» с конкретными данными; для каждого числа — сценарий на границе и сразу за ней.
- Сценарии ссылаются только на существующие требования.
- Маркеров
[УТОЧНИТЬ: …]в тексте не осталось; открытые вопросы либо решены со ссылкой на источник, либо явно вынесены за рамки. - Каждое существительное спецификации и плана есть в глоссарии; синонимов из «Не говорим» нет.
- План упоминает каждый агрегат и каждую сущность и не нарушает конституцию (или обосновывает нарушение).
- У каждой задачи есть требования и зависимости; граф без циклов; каждое требование покрыто задачей.
- У каждого сценария есть приёмочный тест с ID в имени.
Пункты 1–9 — механика, и почти всю её проверяет программа: линтер требований из урока SDD.2.1, граф задач из задания ниже и автопроверка домашней работы. Человеку остаётся то, что программа не умеет: верны ли сами правила, полны ли сценарии, разумны ли решения плана.
Как не утонуть в markdown
Заголовок раздела «Как не утонуть в markdown»Честно о главной претензии к SDD. Биргитта Бёккелер в статье об инструментах SDD на сайте Мартина Фаулера (2025) отмечает, что инструменты вроде Spec Kit порождают много многословных markdown-файлов, которые приходится читать и ревьюить, и что подробная спецификация может давать ложное чувство контроля: агент всё равно может её проигнорировать или понять по-своему. Прочитайте оригинал — ссылка в уроке SDD.1.1; подробно критику разберём в уроке SDD.4.3.
Полный пакет Spec Kit для одной фичи — это spec.md, plan.md, research.md, data-model.md, папка contracts/,
quickstart.md, tasks.md и чек-листы. Для исправления опечатки это абсурд. OpenSpec формулирует противоядие как
принцип «прогрессивной строгости»: облегчённая спецификация по умолчанию — короткие требования о поведении, границы,
несколько проверок приёмки, — а полная только для рискованных изменений: между командами и репозиториями, при смене
API, миграциях, вопросах безопасности. Большинство изменений, по их словам, должны оставаться облегчёнными.
| Задача | Что писать |
|---|---|
| исправление ошибки, мелкая правка | одна запись в задачах: что сломано, как проверить; тест на ошибку |
| небольшая фича на день | spec.md (требования и сценарии) и tasks.md; план — абзацем в спецификации |
| фича на неделю, новые сущности | пакет курса: конституция (уже есть), spec.md, plan.md, tasks.md, глоссарий |
| межкомандная, новый API, деньги, безопасность | полный пакет: контракты, исследование, ADR, чек-листы |
Признаки, что документов слишком много: их никто не читает целиком; одно и то же написано в трёх местах и уже разошлось; агент получает противоречивые указания. Лечение — каждый факт в одном месте, остальные на него ссылаются.
Последний вопрос — что делать с документами после реализации. Spec Kit оставляет его команде и называет три модели:
изменения могут начинаться в любом документе и потом сводиться вручную (flow-back); завершённые папки фич не меняют, а
на каждое изменение заводят новую (flow-forward); или spec.md — единственный источник, а план и задачи
перегенерируют из него (living spec). Выберите модель явно и запишите её в конституцию — иначе через полгода никто не
будет знать, какому документу верить.
Практика
Заголовок раздела «Практика»Задание: граф задач
Заголовок раздела «Задание: граф задач»Напишите инструменты, которые проверяют tasks.md так же, как это сделает автопроверка домашней работы. Функция
parse_tasks уже готова: она разбирает markdown в формате курса в словарь
{"T-001": {"title": ..., "requires": [...], "depends": [...]}}. Готова и format_report, которая печатает отчёт.
Ваши функции:
untraced(tasks)— задачи без ссылок на требования;uncovered(tasks, requirement_ids)— требования, на которые не ссылается ни одна задача;topo_order(tasks)— порядок выполнения алгоритмом Кана (из готовых — наименьший ID), при цикле —ValueError;find_cycle(tasks)— один цикл в виде пути["T-001", "T-003", "T-002", "T-001"]илиNone;waves(tasks)— параллельные волны.
В задании два плана: задачи записи на поток из этого урока и неудачный план с циклом.
import re
# Требования спецификации «Запись на поток» (урок SDD.2.1, сокращённо)
REQUIREMENTS = {
"REQ-001": "запись на поток со свободными местами: статус «ожидает оплаты»",
"REQ-002": "мест нет: отказ NoFreeSeats",
"REQ-003": "повторная запись: отказ AlreadyEnrolled",
"REQ-004": "запись в момент старта или позже: отказ EnrollmentClosed",
"REQ-005": "48 часов без оплаты: запись отменена",
"REQ-006": "отмена за 7 суток и более до старта: полный возврат",
"REQ-007": "отмена позже: без возврата",
"REQ-008": "запись отменена: событие EnrollmentCancelled",
"REQ-009": "деньги — целое число копеек и валюта",
"REQ-010": "ответ на запись не дольше 500 мс для 95% запросов при 20 запросах в секунду",
}
# tasks.md этой спецификации — в том же формате, что в домашней работе sdd-hw01-spec
TASKS_MD = """\
# Задачи: запись на поток
## T-001: Приёмочные тесты по сценариям SC-001…SC-007 (красные)
Требования: REQ-001, REQ-002, REQ-003, REQ-004, REQ-005, REQ-006, REQ-007, REQ-008, REQ-009
Зависит от: —
Готово, когда: pytest находит 7 тестов с ID сценариев в именах, все падают
## T-002: Объект-значение Money
Требования: REQ-009
Зависит от: —
Готово, когда: юнит-тесты Money зелёные: копейки, валюта, сложение только в одной валюте
## T-003: Статусы записи и исключения Набора
Требования: REQ-002, REQ-003, REQ-004
Зависит от: —
Готово, когда: EnrollmentStatus и исключения импортируются из school.enrollment
## T-004: Агрегат Cohort: запись на поток и вместимость
Требования: REQ-001, REQ-002
Зависит от: T-001, T-003
Готово, когда: тесты SC-001 и SC-002 зелёные
## T-005: Запрет повторной записи
Требования: REQ-003
Зависит от: T-004
## T-006: Закрытие записи в момент старта потока
Требования: REQ-004
Зависит от: T-004
## T-007: Отмена неоплаченной записи через 48 часов и событие EnrollmentCancelled
Требования: REQ-005, REQ-008
Зависит от: T-004
## T-008: Отмена студентом и сумма возврата
Требования: REQ-006, REQ-007, REQ-008
Зависит от: T-002, T-007
## T-009: Обновить README пакета enrollment
Требования: —
Зависит от: T-005, T-006, T-008
"""
# Неудачный план: три задачи ждут друг друга по кругу
CYCLIC_MD = """\
## T-001: Сервис записи
Требования: REQ-001
Зависит от: T-003
## T-002: Агрегат Cohort
Требования: REQ-001
Зависит от: T-001
## T-003: Репозиторий потоков
Требования: REQ-001
Зависит от: T-002
## T-004: Исключения Набора
Требования: REQ-002
Зависит от: —
"""
TASK_HEADING = re.compile(r"^#{2,4}\s+(T-\d{3})\b\s*[:.—–-]?\s*(.*)$")
def parse_tasks(markdown):
"""Разбирает tasks.md: {"T-001": {"title": ..., "requires": [...], "depends": [...]}, ...}.
Готовая функция. Задача начинается заголовком «## T-001: название»; поля «Требования:» и
«Зависит от:» — строки под заголовком. ID ищутся регулярными выражениями, поэтому «—» или
«нет» в поле означают пустой список.
"""
tasks = {}
current = None
for line in markdown.splitlines():
heading = TASK_HEADING.match(line.strip())
if heading:
current = heading.group(1)
if current in tasks:
raise ValueError(f"задача {current} описана дважды")
tasks[current] = {"title": heading.group(2).strip(), "requires": [], "depends": []}
continue
if line.startswith("#"):
current = None # другой заголовок: описание задачи кончилось
continue
if current is None:
continue
field = re.sub(r"[*_]", "", line).strip().lstrip("-").strip().lower()
if field.startswith("требования"):
tasks[current]["requires"] = re.findall(r"REQ-\d{3}", line)
elif field.startswith("зависит от"):
tasks[current]["depends"] = re.findall(r"T-\d{3}", line)
return tasks
def missing_dependencies(tasks):
"""Ссылки на несуществующие задачи: {задача: [чего не хватает]}. Готовая функция."""
missing = {}
for task_id, task in tasks.items():
unknown = [dep for dep in task["depends"] if dep not in tasks]
if unknown:
missing[task_id] = unknown
return missing
def untraced(tasks):
"""Задачи без единой ссылки на требование — отсортированный список ID.
Пример: у T-009 поле «Требования: —», значит, untraced(parse_tasks(TASKS_MD)) == ["T-009"].
"""
# TODO: ваш код
...
def uncovered(tasks, requirement_ids):
"""Требования, на которые не ссылается ни одна задача, — отсортированный список ID.
requirement_ids — любой набор ID требований (список или словарь REQUIREMENTS: перебор даёт ключи).
"""
# TODO: ваш код
...
def topo_order(tasks):
"""Порядок выполнения: каждая задача после всех, от которых зависит.
Алгоритм Кана: задача готова, когда все её зависимости уже в порядке. Пока есть готовые,
берите из них задачу с наименьшим ID, добавляйте в порядок и вычёркивайте из зависимостей
остальных. Если невыполненные задачи остались, а готовых нет, — в зависимостях цикл:
raise ValueError(...) со списком задач, которые не удалось упорядочить.
Можно считать, что все зависимости существуют (это проверяет missing_dependencies).
"""
# TODO: ваш код
...
def find_cycle(tasks):
"""Один цикл зависимостей в виде пути ["T-001", "T-003", "T-002", "T-001"] или None, если циклов нет.
В пути каждая задача зависит от следующей, первая и последняя совпадают. Задачи, которые
только ждут цикла (зависят от него), в путь не входят.
"""
# TODO: ваш код
...
def waves(tasks):
"""Параллельные волны: [[задачи первой волны], [второй], ...], ID внутри волны по возрастанию.
Волна задачи = 1, если зависимостей нет, иначе 1 + наибольшая волна её зависимостей.
Задачи одной волны друг от друга не зависят — их можно делать одновременно.
Пустой план — пустой список. При цикле — ValueError (как в topo_order).
"""
# TODO: ваш код
...
def format_report(markdown, requirement_ids):
"""Отчёт по плану задач для человека (готовая функция)."""
def show(items, sep=", "):
return "(функция ещё не написана)" if items is None else (sep.join(items) or "нет")
tasks = parse_tasks(markdown)
lines = [f"Задач: {len(tasks)}, требований: {len(requirement_ids)}"]
missing = missing_dependencies(tasks)
if missing:
lines.append(" Ссылки на несуществующие задачи: " + "; ".join(f"{t} → {', '.join(d)}" for t, d in missing.items()))
return "\n".join(lines)
cycle = find_cycle(tasks)
if cycle:
lines.append(" Цикл в зависимостях: " + " → ".join(cycle))
else:
lines.append(" Порядок: " + show(topo_order(tasks), " → "))
found = waves(tasks)
if found is None:
lines.append(" Волны: (функция ещё не написана)")
for number, wave in enumerate(found or [], start=1):
lines.append(f" Волна {number}: {', '.join(wave)}")
lines.append(" Задачи без требований: " + show(untraced(tasks)))
lines.append(" Требования без задач: " + show(uncovered(tasks, requirement_ids)))
return "\n".join(lines)
print(format_report(TASKS_MD, REQUIREMENTS))
print()
print(format_report(CYCLIC_MD, ["REQ-001", "REQ-002"]))
Посмотрите на отчёт решения. Задача T-009 «Обновить README» без требований — это пункт определения готовности, а не задача. А REQ-010 о времени ответа не покрыто ничем: нагрузочный сценарий SC-009 не попал ни в одну задачу. Именно такие пропуски анализ согласованности ловит раньше, чем их заметит пользователь.
Проверь себя
Заголовок раздела «Проверь себя»- План превращают в маленькие проверяемые задачи: дифф читается за 10–15 минут, есть «Готово, когда», явные зависимости и ссылки на требования; лучше вертикальные срезы, чем горизонтальные слои.
- Граф зависимостей отвечает на три вопроса: порядок (топологическая сортировка, алгоритм Кана), параллельность (волна = 1 + наибольшая волна зависимостей; параллельно — только если разные файлы) и выполнимость (цикл — ошибка плана).
- Трассируемость REQ → SC → T → тест → коммит держится на ID и проверяется программой: требования без задач и задачи без требований находятся автоматически.
- Определение готовности — общий для всех задач чек-лист; конституция проекта (термин Spec Kit) — немногие проверяемые принципы, которые проверяются в плане как контрольная точка и меняются только явной поправкой с версией.
- Чек-листы проверяют качество требований, а не код. Размер пакета документов подбирают под риск задачи: каждый факт — в одном месте.
Дальше: урок SDD.3.1 — GitHub Spec Kit на практике: установка, конституция и полный цикл команд от спецификации до реализации.