Перейти к содержимому
Программа SDDSDD.2 Как писать спецификации

Задачи и «конституция» проекта

Как разбить технический план на маленькие проверяемые задачи с порядком, зависимостями и параллельными волнами, связать каждую задачу с требованиями, задать определение готовности и «конституцию» проекта — неизменные принципы для людей и агентов; чек-листы качества спецификации и сколько markdown действительно нужно.

УРОКsdd/sdd-2-specs/03-tasks-and-constitution.mdx2 ч 30 минУрок 3 из 3
После урока вы сможете
  • Разбивать план на маленькие проверяемые задачи с зависимостями, пометкой параллельности и ссылками на требования
  • Строить порядок выполнения и параллельные волны топологической сортировкой, находить циклы и задачи без требований
  • Формулировать определение готовности и конституцию проекта — проверяемые принципы, а не пожелания
  • Проверять спецификацию чек-листами и выбирать минимальный набор документов под размер задачи

У нас есть спецификация записи на поток (урок 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
Граф зависимостей задач записи на поток: T-004 ждёт тестов и статусов, отмена студентом — объекта Money и отмены по сроку

Это граф зависимостей задачграф зависимостей задачEN task dependency graphОриентированный граф: задачи — вершины, «зависит от» — рёбра. Порядок выполнения даёт топологическая сортировка; цикл в графе означает, что план невыполним и его надо перерезать.: задачи — вершины, «зависит от» — рёбра. Из него отвечают на три вопроса: в каком порядке делать, что можно делать одновременно и нет ли в плане невозможного.

Порядок выполнения: топологическая сортировка

Заголовок раздела «Порядок выполнения: топологическая сортировка»

Порядок, в котором каждая задача идёт после всех своих зависимостей, называется топологической сортировкойтопологическая сортировкаEN topological sortПорядок узлов ориентированного графа без циклов, в котором каждый узел идёт после всех, от кого зависит. backward() обходит его в обратную сторону.. Вы встречали её, если проходили обратное распространение ошибки в micrograd (урок 3.2.2) или алгоритмическую секцию собеседования (урок 11.3.5); здесь она нужна в самом простом виде — алгоритм Кана (Kahn):

  1. Задача готова, если все её зависимости уже выполнены. В начале готовы задачи без зависимостей.
  2. Из готовых берём одну — в курсе ту, у которой меньше ID, чтобы порядок был однозначным, — и добавляем в порядок.
  3. Вычёркиваем её из зависимостей остальных: кто-то из них мог стать готовым. Повторяем с шага 2.

Пройдём по графу выше:

ШагГотовыБерёмЧто открылось
1T-001, T-002, T-003T-001—
2T-002, T-003T-002— (T-008 ждёт ещё T-007)
3T-003T-003T-004
4T-004T-004T-005, T-006, T-007
5–6T-005, T-006, T-007T-005, затем T-006—
7T-007T-007T-008
8T-008T-008T-009
9T-009T-009—

Если задачи остались, а готовых нет, — в графе цикл: T-001 ждёт T-003, T-003 ждёт T-002, T-002 ждёт T-001. Такой план невыполним, и это ошибка плана, а не повод «начать с какой-нибудь». Обычно цикл значит, что две задачи на самом деле одна или что у них есть общая часть, которую надо вынести в отдельную задачу.

Порядок говорит, что за чем, но не что одновременно. Для этого задачи раскладывают на параллельные волныпараллельная волна задачEN parallel group, waveГруппа задач, которые не зависят друг от друга и все зависимости которых уже выполнены, — их можно отдать нескольким людям или агентам одновременно. В Spec Kit такие задачи помечают [P].: волна задачи равна 1, если у неё нет зависимостей, иначе — 1 плюс наибольшая волна её зависимостей. Задачи одной волны друг от друга не зависят:

ВолнаЗадачиПочему здесь
1T-001, T-002, T-003зависимостей нет
2T-004ждёт T-001 и T-003 (волна 1)
3T-005, T-006, T-007все ждут только T-004
4T-008ждёт T-007 (волна 3) и T-002 (волна 1)
5T-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
Цепочка трассируемости: у каждого звена есть ID, и ссылки можно проверить программой

Ссылки дают две проверки, которые делает не человек, а скрипт:

  • требование без задачи — его забыли реализовать. Частая жертва — нефункциональные требования: «отвечать не дольше 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] проверяющий человек, а реализация только читает их состояние как контрольную точку.

Чек-лист для пакета спецификаций в формате курса:

  1. У каждого требования есть ID REQ-…, и одно требование — одно обязательство.
  2. В требованиях нет слов из стоп-списка урока SDD.2.1.
  3. У каждого требования есть сценарий «Дано / Когда / Тогда» с конкретными данными; для каждого числа — сценарий на границе и сразу за ней.
  4. Сценарии ссылаются только на существующие требования.
  5. Маркеров [УТОЧНИТЬ: …] в тексте не осталось; открытые вопросы либо решены со ссылкой на источник, либо явно вынесены за рамки.
  6. Каждое существительное спецификации и плана есть в глоссарии; синонимов из «Не говорим» нет.
  7. План упоминает каждый агрегат и каждую сущность и не нарушает конституцию (или обосновывает нарушение).
  8. У каждой задачи есть требования и зависимости; граф без циклов; каждое требование покрыто задачей.
  9. У каждого сценария есть приёмочный тест с ID в имени.

Пункты 1–9 — механика, и почти всю её проверяет программа: линтер требований из урока SDD.2.1, граф задач из задания ниже и автопроверка домашней работы. Человеку остаётся то, что программа не умеет: верны ли сами правила, полны ли сценарии, разумны ли решения плана.

Честно о главной претензии к 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) — параллельные волны.

В задании два плана: задачи записи на поток из этого урока и неудачный план с циклом.

Граф задач: порядок, циклы, трассируемость и параллельные волны8 тестов
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 не попал ни в одну задачу. Именно такие пропуски анализ согласованности ловит раньше, чем их заметит пользователь.

Проверь себя6 вопросов
1 Какая из задач лучше всего подходит для агента?
2 Задачи: A без зависимостей; B зависит от A; C зависит от A; D зависит от B и C; E без зависимостей. Сколько параллельных волн получится?
3 Алгоритм Кана остановился: готовых задач нет, а T-004, T-005 и T-007 ещё не упорядочены. Что это значит?
4 Что из этого — определение готовности (DoD), а не критерий приёмки? Выберите все подходящие.Можно выбрать несколько ответов
5 Какой пункт годится в конституцию проекта?
6 Какой пункт — правильный пункт чек-листа спецификации в смысле Spec Kit («юнит-тест для требований»)?
  • План превращают в маленькие проверяемые задачи: дифф читается за 10–15 минут, есть «Готово, когда», явные зависимости и ссылки на требования; лучше вертикальные срезы, чем горизонтальные слои.
  • Граф зависимостей отвечает на три вопроса: порядок (топологическая сортировка, алгоритм Кана), параллельность (волна = 1 + наибольшая волна зависимостей; параллельно — только если разные файлы) и выполнимость (цикл — ошибка плана).
  • Трассируемость REQ → SC → T → тест → коммит держится на ID и проверяется программой: требования без задач и задачи без требований находятся автоматически.
  • Определение готовности — общий для всех задач чек-лист; конституция проекта (термин Spec Kit) — немногие проверяемые принципы, которые проверяются в плане как контрольная точка и меняются только явной поправкой с версией.
  • Чек-листы проверяют качество требований, а не код. Размер пакета документов подбирают под риск задачи: каждый факт — в одном месте.

Дальше: урок SDD.3.1 — GitHub Spec Kit на практике: установка, конституция и полный цикл команд от спецификации до реализации.