Методология v1.0 — открытый стандарт
Требования · Домен · Тесты

Требования,
которые живут
вместе с кодом.

Atomic Spec — методология совместной работы PM, разработчика и тестировщика. Один источник правды. Полная история решений. Статус каждого требования — виден без совещания.

1
источник правды
для всей команды
3
состояния файла
draft → active → deprecated
0
дополнительных
инструментов
история каждого
решения в git
Проблема которую мы решаем

Требования живут везде.
Значит нигде.

Сейчас у большинства команд
Требования в Confluence, задачи в Jira, обсуждения в Slack — разрыв контекста постоянный
Новый разработчик не знает почему принято то или иное решение
Тестировщик не знает что именно изменилось в спринте — тестирует всё или ничего
PM меняет требование — разработчик узнаёт на ревью кода, тестировщик — никогда
Руководитель не видит реальный статус: что реализовано, что не проверено, что устарело
С Atomic Spec
Всё в git: требования, решения, тесты, история — один репозиторий, одна история
git log spec.md — полная история: кто, когда, почему, по чьей инициативе
git diff release/v1..release/v2 — точный список что изменилось в сценариях
Amendment-файл фиксирует конфликт изменения со спринтом — никто не застаёт врасплох
Дерево файлов — живой дашборд: draft / active / deprecated видно без открытия файла
Как выглядит в реальности

Дерево файлов — это и есть
состояние системы

📁 specs/auth/
├── _index.md ← дашборд домена
├── domain.spec.md v3.1 · Identity aggregate
├── 📁 _draft/ ← открытые вопросы, нельзя брать в работу
│ └── AUTH-MERGE-010_google-github-linking.spec.md
├── 📁 _deprecated/ ← история, никогда не удаляется
│ └── AUTH-REG-001_email-password-registration.spec.md
└── 📁 registration/
├── AUTH-REG-010_phone-otp-registration.spec.md ✓ active
├── AUTH-REG-020_google-oauth-registration.spec.md ✓ active
└── AUTH-REG-020a~param_otp-timeout.spec.md amendment
Как это работает

Три правила. Вся методология.

01
Атом = один файл

Каждое требование — отдельный .spec.md. Содержит намерение, правила домена, Gherkin-сценарии и платформенный контракт. Добавить требование = создать файл.

02
Позиция = статус

Файл в корне папки — верифицировано и актуально. В _draft/ — есть открытые вопросы. В _deprecated/ — история. Статус виден без открытия файла.

03
Git = журнал решений

Кто изменил, когда, почему, по чьей инициативе — в commit message. Diff между тегами релизов — точный список изменений для каждой роли.

Начать

Выберите роль —
получите полное руководство

Детальные инструкции, примеры файлов, git-команды и сценарии работы — для каждой роли отдельно.

📋 PM / Аналитик ⚙️ Разработчик 🧪 Тестировщик 🎯 Руководитель
Методология работы с живыми требованиями
📋 PM · Аналитик требований

Вы всегда знаете
актуальное состояние
требований

Не «кажется всё согласовали», а точный ответ: что верифицировано, что в работе, что нужно решить прямо сейчас.

Полное руководство → ← Назад
Боли которые мы закрываем
😰
«Мы это обсуждали в Slack»
Решение принято, но через месяц никто не помнит почему. Разработчик делает по-своему, потому что «логичнее».
🔄
Изменение требования в спринте
Вы уточнили требование — часть команды узнала сразу, часть — на демо. Amendment-файл фиксирует конфликт явно.
Незакрытые вопросы
Open Questions лежат в _draft/ и видны всем. Не уйдут в реализацию пока не решены. Не потеряются в переписке.
Ваш рабочий процесс

Как аналитик работает с атомами

Шаг 1
Создать атом в _draft/
Шаг 2
Закрыть Open Questions
Шаг 3
PR → верификация
Шаг 4
Merge → корень папки
Шаг 5
Deprecated при замене
specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md
--- id: AUTH-REG-020 type: use-case title: "Регистрация через Google OAuth" parent: AUTH # Открытые вопросы — причина быть в _draft/ open-questions: - id: OQ-1 question: "Обязателен ли merge при совпадении email?" status: open ← файл в _draft/ пока status: open implementation: status: none verification: status: none blocks-release: true --- ## § Intent Гость аутентифицируется через Google. Если аккаунта нет — создаётся. Если есть — выполняется вход. Один flow, два исхода. ## § Domain Rules DR-O-1 Email от Google считается подтверждённым → роль User (не UnverifiedUser) DR-O-2 Если email совпадает с Phone-аккаунтом → см. AUTH-MERGE-001 ## § Acceptance Criteria Scenario: Новый пользователь через Google Given нет аккаунта с данным googleId When гость проходит OAuth flow Then создаётся User с ролью User And эмитится UserRegistered
Ключевые возможности
  • Decision Log в каждом атоме
    Каждое решение записывается с датой, участниками и альтернативами. Через полгода понятно почему, а не только что.
  • Типы изменений с правилами
    ParameterChange, RuleChange, ModelChange — каждый тип знает кто должен верифицировать и блокирует ли спринт.
  • Deprecated не значит удалён
    История всегда сохранена. Можно проследить эволюцию от email/password → phone/OTP → OAuth.
  • Amendment при конфликте со спринтом
    Изменение пришло пока идёт разработка? Создаётся amendment-файл с явным решением: изъять или сделать следующим PR.
Полное руководство аналитика →
⚙️ Разработчик

Вы точно знаете
что и почему
нужно сделать

Не «кажется так договорились», а точная дельта: что изменилось в требованиях пока ты делал задачу, и кто это решил.

Полное руководство → ← Назад
Что вы получаете
🎯
Что делать прямо сейчас
Открытый PR = ваша задача. Diff PR vs main = точно что нужно реализовать. Никаких «уточни у Маши».
📖
Почему именно так
git log spec.md — полная история решений. Decision Log объясняет почему не выбрали альтернативу.
Изменение требования
Amendment-файл в _ready/ сигнализирует: требование изменилось пока вы делали. Явное решение в файле.
Ключевые git-команды разработчика

Работа с веткой как источником правды

terminal
# Что мне нужно реализовать в этом спринте git log --oneline main..HEAD -- specs/ # Изменилось ли требование пока я делал задачу git diff main feat/AUTH-REG-020 -- specs/auth/ # Почему было принято такое решение git log --follow -p specs/auth/registration/AUTH-REG-020.spec.md # Что изменилось относительно прошлого релиза git diff release/Sprint-13..release/Sprint-14 -- specs/ # Все незакрытые amendmentы grep -r "amendment-status: pending" specs/ -l
Правило разработчика Перед началом реализации: git diff main HEAD -- specs/ — убедиться что требование в вашей ветке актуально. Если аналитик внёс изменение в main пока вы работали — вы увидите diff.
Тесты внутри спека

Платформенный контракт — прямо в файле требований

AUTH-REG-020.spec.md → § Platform: Web API
## § Platform: Web API contract: method: POST path: /v1/auth/oauth/callback body: provider: string # "google" | "github" code: string # OAuth authorization code responses: 201: { userId, email, provider, isNewUser: true } 200: { userId, email, provider, isNewUser: false } 409: { code: ACCOUNT_MERGE_REQUIRED } ## § Platform Tests // @spec AUTH-REG-020 | platform: web-api describe('POST /v1/auth/oauth/callback', () => { it('201 → новый юзер создан с ролью User', async () => { const res = await api.post('/v1/auth/oauth/callback', { provider: 'google', code: 'valid-code' }) expect(res.status).toBe(201) expect(res.body.isNewUser).toBe(true) expect(events).toContainEvent('UserRegistered') }) })
Полное руководство разработчика →
🧪 Тестировщик · QA

Вы знаете точно
что изменилось
в этом релизе

Не «протестируй всё», а точный diff сценариев между тегами. Что новое, что изменилось, что блокирует релиз.

Полное руководство → ← Назад
Три ключевых вопроса закрыты
📋
Что тестировать в релизе
git diff release/v1..release/v2 -- specs/ — точный список файлов и дельта сценариев. Ничего лишнего.
🚦
Что блокирует выход
blocks-release: true + verification.status: none — список именно этих файлов. Всё остальное — второстепенно.
🔍
Что именно изменилось
Diff секции § Acceptance Criteria между релизами показывает не весь файл, а только изменения в поведении.
Пример рабочего процесса

Что изменилось в Sprint 14

terminal — тестировщик работает с тегами релизов
# 1. Что появилось или изменилось в этом спринте git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-only # Вывод: specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md ← новый specs/auth/otp/AUTH-OTP-002_rate-limit-exceeded.spec.md ← изменён # 2. Что именно изменилось в сценариях OTP git diff release/Sprint-13..release/Sprint-14 \ -- specs/auth/otp/AUTH-OTP-002.spec.md # Вывод: - When повторный запрос приходит ранее чем через 20 секунд + When повторный запрос приходит ранее чем через 60 секунд # 3. Что блокирует релиз grep -r "blocks-release: true" specs/ -l | \ xargs grep -l "verification.status: none" # Вывод — срочно тестировать: specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md
Полное руководство тестировщика →
🎯 Руководитель проектно-продуктового офиса

Полная картина
без совещаний
и статус-отчётов

Состояние каждого домена, открытые решения, риски спринта — в любой момент, прямо из репозитория.

Полное руководство → ← Назад
Метрики которые вы видите
24
активных требования в системе
3
открытых решения блокируют прогресс
2
требования блокируют релиз
6
deprecated — история изменений

Все цифры — из одной команды. Реальное состояние, не то что написано в отчёте.

Что видит руководитель
🗺️
Эволюция доменов
Git-теги доменов показывают историю архитектурных решений. domain/AUTH/v1.0 → v3.1 — три Breaking, одно Additive.
⚠️
Риски спринта
HIGH-risk use-cases — в отдельных PR. Amendment со sprint-locked — явный конфликт требования с текущей разработкой.
📊
Готовность к релизу
Список файлов с blocks-release: true и verification: none — точный ответ: можно ли выходить.
Полное руководство руководителя →

Роль аналитика в Atomic Spec

Аналитик — владелец атомов. Он создаёт требования, закрывает открытые вопросы, верифицирует изменения доменной модели. Вся работа аналитика — в .spec.md файлах и PR.

Главное правило Если требование существует, оно должно быть в атоме. Если требование изменилось — создаётся amendment или обновляется атом с changelog-записью в commit message.

Жизненный цикл атома

Каждый атом проходит путь через позиции в дереве файлов:

_draft/
Создание и работа с Open Questions
Файл в _draft/ пока есть хоть один OQ со status: open. Нельзя брать в разработку.
PR review
Верификация стейкхолдерами
PR переносит файл из _draft/ в корень. Стейкхолдеры апрувят PR — это и есть верификация.
корень
Верифицировано, активно
Файл в корне = можно брать в спринт. Разработчик и тестировщик работают с ним.
_deprecated/
История навсегда
Файл никогда не удаляется. deprecated-by ссылается на замену. Причина записана в commit.

Формат атома — полный шаблон

specs/[domain]/[section]/DOMAIN-TYPE-NNN_slug.spec.md
--- id: AUTH-REG-020 type: use-case # use-case | scenario | domain | amendment title: "Регистрация через Google OAuth" parent: AUTH children: - AUTH-REG-021_google-provider-error - AUTH-REG-022_email-conflict # Доменные связи see-also: [AUTH-MERGE-001, AUTH-OTP-001] emits: [UserRegistered, UserLoggedIn] # Статусы по трём осям (аналитик управляет первым) open-questions: - id: OQ-1 question: "Обязателен ли merge при совпадении email?" status: resolved resolution: "Merge запрещён без явного подтверждения пользователя" resolved-by: "Maria K. / Product" implementation: status: none # none | in-progress | done verification: status: none # none | in-progress | passed | failed blocks-release: true --- ## § Intent Гость аутентифицируется через Google OAuth. ## § Domain Rules DR-O-1 Email от Google считается подтверждённым → роль User (не UnverifiedUser) DR-O-2 providerUserId уникален per provider DR-O-3 Если email совпадает с существующим аккаунтом → AUTH-MERGE-001 ## § Acceptance Criteria Scenario: Успешная регистрация нового пользователя Given нет аккаунта с данным googleId When гость проходит OAuth flow с Google Then создаётся User с ролью User And создаётся Identity { provider: google } And эмитится UserRegistered ## § Decision Log D-001 date: 2024-05-10 decision: "Email от OAuth считается подтверждённым автоматически" rationale: "Google гарантирует верификацию email на своей стороне" alternatives: ["Требовать повторную верификацию"] decided-by: "Maria K. + Ivan P."

Типы изменений и правила

ТипКогдаКто верифицируетБлокирует спринт
ParameterChangeИзменение значения: таймаут, лимит, размер1 стейкхолдернет
RuleChangeНовое, изменённое или удалённое доменное правилоProduct + Techвозможно
FlowChangeНовый шаг в сценарии, изменение веткиProductвозможно
ModelChangeНовый агрегат, поле, событиеProduct + Tech + Architectда
BoundaryChangeПеренос агрегата, разделение доменаCTO + Architect + Productда, RFC

Правки когда требование изменилось в спринте

Если требование изменилось пока идёт разработка — не правим исходный атом, создаём amendment:

AUTH-OTP-002a~param_retry-timeout.spec.md
--- id: AUTH-OTP-002a type: amendment change: ParameterChange title: "OTP retry timeout: 20с → 60с" amends: AUTH-OTP-002_rate-limit-exceeded parameter: name: retry_timeout_seconds was: 20 becomes: 60 conflict: status: sprint-locked # sprint-locked | pulled | merged locked-sprint: Sprint-14 resolution: "Доделать Sprint-14 со старым значением, amendment → Sprint-15" ---
Три сценария 1. Атом ещё в _draft/ → просто обновить файл на месте.
2. В _in-progress/, успели изъять → вернуть в _ready/, обновить.
3. В разработке / уже готово → создать amendment-файл рядом.

Git-конвенции для аналитика

commit message convention
# Формат commit message [AUTH-REG-020][Additive] Регистрация через Google OAuth Requested-by: Maria K. / Product Reason: OAuth initiative Q2 2024 Decision: D-001 (email от Google = verified автоматически) Stakeholders: Maria K. (Product), Ivan P. (CTO) Breaking: false Affects: AUTH-MERGE-001 # PR title [AUTH-REG-020] feat: Google OAuth registration # PR description включает чеклист верификации ## Verified by - [x] Maria K. / Product - [x] Ivan P. / CTO ## Open Questions resolved - OQ-1: merge запрещён без подтверждения ✓

Работа с Open Questions

Open Questions — единственная причина находиться в _draft/. Пока есть хоть один status: open, файл не переезжает в корень.

Статус OQЧто значитДействие
openВопрос открыт, решение не принятоФайл остаётся в _draft/
blockedЗависит от другого OQФайл остаётся в _draft/
resolvedРешение принято, зафиксированоЕсли все resolved → PR в корень

Частые сценарии работы

Новый метод авторизации

  1. Создать AUTH-REG-030_github-oauth-registration.spec.md в _draft/
  2. Зафиксировать открытые вопросы как OQ-N
  3. Провести сессию с командой — закрыть OQ
  4. PR: перенести в корень, стейкхолдеры апрувят
  5. Если GitHub специфика (нет email) — отдельный leaf-атом

Изменение доменной модели

  1. Создать AUTH~MODEL-003~model_identity-aggregate.spec.md в _draft/
  2. Зафиксировать § Model Delta (diff агрегатов)
  3. Указать affects-atoms — все затронутые use-cases
  4. Получить 3 верификатора: Product + Tech Lead + Architect
  5. Отдельный PR до начала спринта, который использует новую модель

Роль разработчика в Atomic Spec

Разработчик — потребитель атомов. Он читает требования, реализует платформенный контракт, обновляет implementation.status и сигнализирует о конфликтах между требованием и реализацией.

Золотое правило Перед началом любой задачи: git diff main HEAD -- specs/. Убедись что требование в твоей ветке актуально. Аналитик мог изменить что-то в main пока ты работал.

Ежедневная работа

Утром нового рабочего дня — три команды:

terminal
# 1. Что изменилось в specs пока меня не было git log main --since="yesterday" -- specs/ --oneline # 2. Есть ли amendment к моей задаче grep -r "amends: AUTH-REG-020" specs/ -l # 3. Дельта требований в моей ветке vs main git diff main feat/AUTH-REG-020 -- specs/

Как читать атом

Разработчик читает секции сверху вниз, останавливаясь на нужном уровне детализации:

СекцияЧто даёт разработчикуОбязательно
§ IntentПонять зачем эта фича вообщеда
§ Domain RulesИнварианты и бизнес-правила — нельзя нарушитьда
§ Acceptance CriteriaGherkin — что должно работать (основа тестов)да
§ Domain Model TouchКакие агрегаты создаются/изменяютсяда
§ Platform: Web APIТочный контракт API — endpoint, body, responsesда
§ Platform TestsГотовые тест-кейсы для реализацииберём как есть
§ Decision LogПочему именно так — контекст решенийпри вопросах

Работа с ветками и PR

git workflow разработчика
# 1. Начало задачи — ветка по ID атома git checkout -b feat/AUTH-REG-020_google-oauth-registration # 2. Обновить implementation.status в атоме # implementation.status: none → in-progress git commit -m "[AUTH-REG-020] wip: начало реализации Google OAuth" # 3. По завершении — обновить статус # implementation.status: in-progress → done git commit -m "[AUTH-REG-020] done: Google OAuth registration implemented PR: #341 Implements: AUTH-REG-020, AUTH-REG-021" # 4. PR title включает ID атома # [AUTH-REG-020] feat: Google OAuth registration

Platform секция — источник контракта

Секция § Platform: Web API — это официальный контракт. Не надо спрашивать аналитика «какой endpoint», не надо смотреть в Swagger. Всё в файле.

§ Platform: Web API
contract: method: POST path: /v1/auth/oauth/callback body: provider: string # "google" | "github" code: string # Authorization code от провайдера state: string # PKCE state для верификации responses: 201: { userId, email?, provider, token, isNewUser: true } 200: { userId, email?, provider, token, isNewUser: false } 409: { code: ACCOUNT_MERGE_REQUIRED, mergeToken } 502: { code: PROVIDER_ERROR, provider } # Платформа Mobile — отдельная секция § Platform: Mobile ui-flow: OAuthWebView → LoadingScreen → HomeScreen deep-link: myapp://auth/callback

Когда требование изменилось пока вы делаете

Аналитик создал amendment-файл рядом с вашим атомом. Что делать:

  1. Прочитать conflict.status в amendment
  2. Если sprint-locked → доделать текущую задачу как есть, amendment — следующий PR
  3. Если pulled → задача возвращена, нужно обновить реализацию
  4. Обновить amendment-status после своего решения
Никогда не молчи об amendment Если аналитик создал amendment к твоей активной задаче — это явный сигнал. Обсудите в PR комментарии или в следующем стендапе. Зафиксируйте решение в conflict.resolution.

Полный список git команд

git cheatsheet — разработчик
# Что мне делать в этом спринте git log --oneline main..HEAD -- specs/ # Изменилось ли требование в main пока я делал git diff main feat/AUTH-REG-020 -- specs/ # История конкретного атома (кто, когда, почему менял) git log --follow -p specs/auth/registration/AUTH-REG-020.spec.md # Все активные amendmentы (которые нужно реализовать) grep -r "amendment-status: pending" specs/ -l # Проверить что все мои спеки имеют implementation: done grep -r "implementation.status: in-progress" specs/ -l # История домена — все Breaking изменения git log --tags --simplify-by-decoration --pretty="%D %s" | grep domain/AUTH

Частые сценарии

Аналитик не описал edge case

Создай leaf-атом в _draft/ самостоятельно, открой OQ, попроси аналитика закрыть. Не додумывай поведение самостоятельно — зафиксируй вопрос.

Требование противоречит другому требованию

Добавь see-also ссылку, создай OQ в обоих атомах, заблокируй PR до решения. Конфликт должен быть виден аналитику.

Изменение доменной модели (ModelChange)

Жди пока аналитик смержит ~model_ атом в main. Начинай реализацию только после. ModelChange блокирует спринт — это нормально.

Роль тестировщика в Atomic Spec

Тестировщик — верификатор атомов. Он проверяет что реализованное соответствует сценариям в § Acceptance Criteria, обновляет verification.status и блокирует релиз при несоответствии.

Главный вопрос тестировщика Не «что мне тестировать», а «что изменилось в сценариях между этим и прошлым релизом». Ответ — git diff release/v1..release/v2 -- specs/.

Подготовка к тестированию релиза

terminal — начало тестирования Sprint-14
# 1. Список изменённых спеков в этом релизе git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-status # Вывод: A specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md A specs/auth/registration/AUTH-REG-021_google-provider-error.spec.md M specs/auth/otp/AUTH-OTP-002_rate-limit-exceeded.spec.md # 2. Что именно изменилось в Acceptance Criteria git diff release/Sprint-13..release/Sprint-14 \ -- specs/auth/otp/AUTH-OTP-002.spec.md # 3. Полный список что блокирует релиз grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none" # 4. Что уже прошло проверку grep -rl "verification.status: passed" specs/

Читаем дельту сценариев

Diff секции § Acceptance Criteria — это и есть список что нужно перепроверить:

git diff — изменения в сценарии OTP
## § Acceptance Criteria Scenario: Повторный запрос OTP раньше таймаута Given пользователь запросил OTP - When повторный запрос приходит ранее чем через 20 секунд + When повторный запрос приходит ранее чем через 60 секунд Then система отклоняет запрос с кодом RATE_LIMITED And возвращает время до следующей попытки # Новый сценарий добавлен: + Scenario: Превышен лимит попыток ввода OTP + Given пользователь ввёл неверный OTP 3 раза + Then аккаунт блокируется на 15 минут + And эмитится AccountTemporarilyBlocked

Тестировщик видит: старый тест с 20с нужно обновить на 60с. Добавился новый сценарий с блокировкой аккаунта.

Матрица блокирующих тестов

blocks-releaseverificationДействие
truenone🔴 СРОЧНО — без этого нельзя выходить
truein-progress🟡 В работе — отслеживать
truepassed✅ Готово — не блокирует
falsenone⬜ Можно после релиза
truefailed🔴 СТОП — релиз заблокирован

Обновление статуса верификации

AUTH-REG-020.spec.md — обновляем после тестирования
verification: status: passed # none → in-progress → passed/failed blocks-release: true verified-by: "Kate M. / QA" verified-at: 2024-06-20 run-id: "Sprint-14-regression-001" # Commit message при обновлении [AUTH-REG-020][verification] passed — Google OAuth registration Verified-by: Kate M. / QA Sprint: Sprint-14 Run: Sprint-14-regression-001

Git команды тестировщика

git cheatsheet — тестировщик
# Что нового в релизе git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-status # Только дельта сценариев (§ Acceptance Criteria) git diff release/Sprint-13..release/Sprint-14 -- specs/ | \ grep -A 20 "Acceptance Criteria" # Блокирует ли что-то релиз прямо сейчас grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none\|verification.status: failed" # Что уже протестировано в этом спринте git log release/Sprint-13..HEAD --grep="verification" --oneline # Аmendment которые влияют на сценарии find specs/ -name "*~*" -not -path "*_deprecated*"

Частые сценарии

Реализовали не то что в сценарии

Обновить verification.status: failed, добавить комментарий с деталями, создать баг-репорт со ссылкой на атом (spec: AUTH-REG-020). Если blocks-release: true — релиз блокируется автоматически при следующей проверке.

Сценарий неполный или некорректный

Открыть OQ в атоме (аналитик получает сигнал), не закрывать верификацию как passed. Поведение системы должно соответствовать сценарию — а не наоборот.

Amendment изменил значение в сценарии

Старый тест (20с) нужно обновить на новое значение (60с). Amendment-файл содержит точное was / becomes — используй как инструкцию к изменению теста.

Роль руководителя в Atomic Spec

Руководитель — читатель состояния системы. Атомы дают ему полную картину без необходимости собирать статусы с команды: что реализовано, что не проверено, какие решения заблокированы, какие риски несёт текущий спринт.

Главный принцип Дерево файлов и git-теги доменов — это единственный источник правды о состоянии системы. Если чего-то нет в .spec.md файлах, этого не существует.

Дашборд состояния — пять команд

terminal — утренний мониторинг состояния
# Сколько активных требований в системе find specs/ -name "*.spec.md" \ ! -path "*/_draft/*" \ ! -path "*/_deprecated/*" | wc -l # Незакрытые решения — блокируют прогресс grep -r "status: open" specs/ --include="*.spec.md" | \ grep "open-questions" | wc -l # Что блокирует ближайший релиз grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none" 2>/dev/null # Реализовано но не протестировано grep -rl "implementation.status: done" specs/ | \ xargs grep -l "verification.status: none" 2>/dev/null # Amendmentы в ожидании — конфликты со спринтами find specs/ -name "*~*" ! -path "*/_deprecated/*" | \ xargs grep -l "amendment-status: pending" 2>/dev/null

Риски текущего спринта

Три сигнала которые требуют внимания руководителя:

СигналЧто значитДействие
sprint-locked в amendmentТребование изменилось в середине спринтаПодтвердить решение: изъять или amendment в следующий
HIGH risk в use-caseФича с высоким риском в текущем спринтеВыделить отдельный PR, дополнительный review
ModelChange не смерженАрхитектурное изменение блокирует спринтПриоритизировать review доменного изменения

Эволюция домена — история решений

история домена AUTH
# История версий домена git log --tags --simplify-by-decoration --pretty="%ci %D %s" | grep "domain/AUTH" # Вывод — полная история архитектурных решений: 2024-02-01 domain/AUTH/v2.0 Breaking: email/password → phone/OTP 2024-05-01 domain/AUTH/v3.0 Breaking: Identity aggregate introduced 2024-08-01 domain/AUTH/v3.1 Additive: GitHub OAuth provider # Что изменилось в домене между версиями git diff domain/AUTH/v2.0..domain/AUTH/v3.0 -- specs/auth/domain.spec.md # Тэги версионирования # vX.0 — ModelChange или BoundaryChange (Breaking) # vX.Y — RuleChange или FlowChange (Additive) # Нет PATCH — в требованиях нет "исправлений" без смысла

Готовность к релизу

Перед каждым релизом — четыре проверки:

  1. Нет блокирующих непротестированных требований
    blocks-release: true + verification: none → список пуст
  2. Нет failed верификаций
    verification.status: failed → 0
  3. Нет pending amendmentов к реализованным фичам
    amendment-status: pending в Done задачах → 0
  4. Нет open OQ в активных атомах
    OQ status: open вне _draft/ → аномалия, требует внимания

Метрики здоровья команды

МетрикаКак считатьНорма
Ratio draft/activeФайлов в _draft / всего активных< 20% — нет долгих неопределённостей
Amendment frequencyAmendment-файлов за спринт0-1 — требования стабильны до спринта
Verification lagДней от implementation: done до verification: passed< 5 дней
OQ resolution timeСреднее время от open до resolved< 3 дней — нет застрявших решений
Breaking per quarterModelChange + BoundaryChange тегов< 3 — архитектура стабильна

Git команды руководителя

git cheatsheet — руководитель
# Полная картина: active / draft / deprecated echo "Active:" && find specs/ -name "*.spec.md" ! -path "*/_*" | wc -l echo "Draft:" && find specs/ -name "*.spec.md" -path "*/_draft/*" | wc -l echo "Depr.:" && find specs/ -name "*.spec.md" -path "*/_deprecated/*" | wc -l # Что изменилось в системе за последние 2 недели git log --since="2 weeks ago" -- specs/ --oneline # Проверить готовность к релизу (4 проверки) echo "=== RELEASE READINESS ===" && \ echo "Blocking untested:" && \ grep -rl "blocks-release: true" specs/ | xargs grep -l "verification.status: none" 2>/dev/null && \ echo "Failed verification:" && \ grep -rl "verification.status: failed" specs/ 2>/dev/null && \ echo "Pending amendments:" && \ find specs/ -name "*~*" ! -path "*/_deprecated/*" | xargs grep -l "amendment-status: pending" 2>/dev/null && \ echo "Open OQ outside draft:" && \ grep -r "status: open" specs/ --include="*.spec.md" ! -path "*/_draft/*" -l 2>/dev/null && \ echo "=== ALL CLEAR if no output above ===" # История Breaking изменений за квартал git log --since="3 months ago" --grep="Breaking: true" --oneline -- specs/

Что такое Atomic Spec

Atomic Spec — методология совместной работы над требованиями, которая объединяет четыре подхода в одну систему хранения знаний в git-репозитории.

ПодходЧто берём
Domain-Driven DesignДоменную модель (агрегаты, события, bounded context) как источник общего языка команды
Test-Driven DevelopmentКритерии приёмки (Gherkin) внутри самого требования — тест и требование неразделимы
Use Case DrivenСценарии использования как атомарная единица функциональности
Requirements-DrivenТребования как живые артефакты с историей, не замороженные документы

Центральная идея: один файл — одна единица знания. Атомы складываются в иерархию, ссылаются друг на друга и содержат всё — от бизнес-намерения до тест-кейсов и платформенного контракта.

Главный тест Можно ли восстановить полную историю домена — кто, когда, почему принял каждое решение — только по git log на *.spec.md файлах? Да → методология применена правильно.

Зачем это нужно

Пять проблем, которые закрывает Atomic Spec:

ПроблемаКак закрывает
Требования разбросаны по Confluence, Jira, SlackВсё в одном .spec.md файле рядом с кодом
Непонятно почему принято решение§ Decision Log и git log — полная история
Тестировщик не знает что изменилосьgit diff release/v1..release/v2 — точная дельта сценариев
PM изменил требование в спринте — команда не знаетAmendment-файл с явным conflict.status
Руководитель не видит реальный статусДерево файлов = живой дашборд без совещаний

Иерархия атомов

иерархия
System system.spec.md ← границы всей системы └── Domain domain.spec.md ← bounded context, модель, события └── Use Case AUTH-REG-010_slug.spec.md ← функциональный сценарий └── Scenario AUTH-REG-011_slug.spec.md ← атомарный тест-кейс (leaf)
УровеньФайлЦена измененияRFC
Systemsystem.spec.mdКритическая — затрагивает все доменыобязательно
Domaindomain.spec.mdВысокая — 3+ верификаторажелательно
Use CaseDOMAIN-TYPE-NNN_slug.spec.mdСредняя — 2 верификаторанет
ScenarioDOMAIN-TYPE-NNN_slug.spec.mdНизкая — 1 верификаторнет

Анатомия атома

Файл = YAML frontmatter + Markdown-секции. Секции идут от абстрактного к конкретному. Каждая роль читает свои секции — никто не вынужден читать всё.

структура атома
--- YAML frontmatter --- ## § Intent ← PM/Аналитик: бизнес-намерение, зачем это нужно ## § Domain Rules ← PM/Аналитик: DR-N — бизнес-правила и инварианты ## § Acceptance Criteria ← Тестировщик: Gherkin, технологически нейтральный ## § Domain Model Touch ← Dev + Аналитик: агрегаты, поля, события ## § Constraints ← PM/Dev: нефункциональные (PERF, SEC, IDMP) ## § Platform: Web API ← Dev: endpoint, body, responses — контракт ## § Platform Tests ← Тестировщик: готовые тест-кейсы для платформы ## § Open Questions ← Все: OQ-N — незакрытые вопросы (причина draft) ## § Decision Log ← Все: D-N — принятые решения с альтернативами

Именование файлов

анатомия имени файла
AUTH-REG-010_phone-otp-registration.spec.md ^^^^ ^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^ │ │ │ slug: действие-существительное (читается как описание) │ │ порядковый номер — шаг 10 (шаг 10, следующий 020, резерв между) │ тип: REG=registration, LOG=login, OTP, MERGE, PAY, ORDER... домен — все заглавные, 2–6 букв # Примеры правильных slug email-already-taken ← существительное + прилагательное phone-otp-registration ← тема + действие google-oauth-login ← провайдер + метод + действие payment-3ds-verification ← технология + действие # Правки (amendments) — суффикс после id AUTH-OTP-002a~param_retry-timeout.spec.md ← ParameterChange AUTH-REG-010b~rule_attempt-limit.spec.md ← RuleChange AUTH-REG-010c~flow_add-resend-step.spec.md ← FlowChange AUTH~MODEL-003~model_identity-aggregate.spec.md ← ModelChange ~boundary_extract-otp-domain.rfc.md ← BoundaryChange RFC

Папки и статусы

Три позиции определяют состояние требования. Файл всегда находится ровно в одной из них.

структура папок
feature/ _draft/ ← есть открытые OQ / не верифицировано нельзя брать в разработку файл переезжает в корень когда все OQ = resolved _deprecated/ ← выведено из эксплуатации НИКОГДА не удалять — это история решений deprecated-by ссылается на замену файл.spec.md ← верифицировано, актуально, готово к разработке единственный источник правды для команды
ВопросОтвет без открытия файла
Можно взять в работу?Файл в корне папки → да
Есть открытые вопросы?Файл в _draft/ → нет, стоп
Требование устарело?Файл в _deprecated/ → история
Сейчас в разработке?Открытый PR в git
Реализовано?implementation.status: done + merged PR
Протестировано?verification.status: passed

Каждая директория содержит _index.md — таблицу всех файлов с быстрым статусом. Это живой дашборд директории.

Frontmatter — полная спецификация

Frontmatter содержит только то, что нельзя узнать из git: структурные связи между атомами, открытые вопросы и статусы по трём осям (смысловой, реализационной, тестовой).

полный frontmatter
--- # ── Идентификация ────────────────────────────────────── id: AUTH-REG-010 type: use-case # use-case | scenario | domain | amendment title: "Регистрация Phone/OTP" # ── Иерархия — гит этих связей не знает ─────────────── parent: AUTH children: - AUTH-REG-011_phone-already-taken - AUTH-REG-012_otp-expired - AUTH-REG-013_otp-invalid - AUTH-REG-014_registration-success # ── Доменные связи ───────────────────────────────────── supersedes: AUTH-REG-001_email-password-registration see-also: [AUTH-OTP-001, AUTH-MERGE-001] emits: [UserRegistered] consumes: [OTPCode] # ── Открытые вопросы — единственная причина быть в _draft/ open-questions: - id: OQ-1 question: "Блокировать аккаунт или только сессию при 3 ошибках?" status: resolved # open | resolved | blocked resolution: "Блокировать аккаунт на 15 минут" resolved-by: "Maria K. / Product, 2024-03-15" # ── Ось 1: смысловой статус (аналитик) ───────────────── status: active # draft | active | deprecated # ── Ось 2: реализационный статус (разработчик) ───────── implementation: status: done # none | in-progress | done sprint: Sprint-12 pr: github.com/org/repo/pull/289 # ── Ось 3: тестовый статус (тестировщик) ─────────────── verification: status: passed # none | in-progress | passed | failed blocks-release: true verified-by: "Kate M. / QA, 2024-04-01" ---
Что НЕ хранится в frontmatter Кто изменил, когда изменил, почему изменил, кто запросил — всё это в git log и PR description. Дублировать это в файле не нужно.

Типы изменений

Каждое изменение требования классифицируется перед внесением. Тип определяет: кто верифицирует, блокирует ли спринт, нужен ли RFC, какой радиус затронутых атомов.

ТипЧто меняетсяПримерВерификаторыБлокирует спринт
ParameterChangeЗначение константыТаймаут 20с → 60с1 стейкхолдернет
RuleChangeДоменное правило DRДобавить лимит попытокProduct + Techвозможно
FlowChangeШаг сценария, веткаДобавить шаг resendProductвозможно
ModelChangeАгрегат, поле, событиеНовый агрегат IdentityProduct + Tech + Archда
BoundaryChangeГраницы доменаВыделить OTP-доменCTO + Arch + Productда + RFC
PlatformChangeAPI-контрактНовое поле в responseTech Leadнет
Ключевое отличие от semver Semver решает: «могу ли я обновиться не меняя свой код?» В требованиях вопрос другой: «что мне нужно пересмотреть после этого изменения?» Для Platform-уровня semver подходит полностью. Для домена — нужна классификация Breaking / Additive, без PATCH (в требованиях нет «исправлений» без смыслового изменения).

Правки (amendments)

Когда требование изменяется пока атом уже находится в разработке — правка не вносится в исходный файл. Создаётся отдельный amendment-файл с явным указанием конфликта.

AUTH-OTP-002a~param_retry-timeout.spec.md
--- id: AUTH-OTP-002a type: amendment change: ParameterChange title: "OTP retry timeout: 20с → 60с" amends: AUTH-OTP-002_rate-limit-exceeded parameter: name: retry_timeout_seconds was: 20 becomes: 60 conflict: status: sprint-locked # sprint-locked | pulled | merged locked-sprint: Sprint-14 resolution: "Доделать Sprint-14 со старым значением. Правка → Sprint-15" decided-by: "Alex S. / Dev + Maria K. / Product, 2024-06-10" amendment-status: pending # pending | merged ---

Три сценария при конфликте правки со спринтом:

СитуацияДействие
Атом ещё в _draft/ или _ready/Обновить файл на месте, записать в changelog commit
В разработке, успели изъятьВернуть в _ready/, обновить, пересмотреть спринт
В разработке или уже готовоСоздать amendment-файл рядом, conflict.status: sprint-locked

Версионирование доменов

Разные уровни системы версионируются по-разному, потому что у них разные потребители и разная семантика совместимости.

УровеньСхемаЛогика
DomainvMAJOR.MINOR git-тегMAJOR = ModelChange/BoundaryChange. MINOR = RuleChange/Additive. Нет PATCH.
Use Case / ScenarioРевизия r1, r2, r3Линейная история. Потребитель смотрит diff, не «совместим ли я».
Platform (API)Честный semverПотребитель — код. Совместимость — техническая. Semver работает идеально.
AmendmentНет версииЭто событие, не артефакт. Живёт в git как commit.
примеры тегов
# Домен AUTH — история архитектурных решений git tag domain/AUTH/v1.0 -m "Initial: email/password auth" git tag domain/AUTH/v2.0 -m "Breaking: email→phone/OTP. D-001." git tag domain/AUTH/v3.0 -m "Breaking: Identity aggregate. D-002." git tag domain/AUTH/v3.1 -m "Additive: GitHub OAuth provider" # Platform API — честный semver git tag platform/web-api/v1.0.0 -m "Initial API" git tag platform/web-api/v1.1.0 -m "Added provider field to response" git tag platform/web-api/v2.0.0 -m "Breaking: changed auth callback contract" # Просмотр истории домена git log --tags --simplify-by-decoration --pretty="%ci %D %s" | grep "domain/AUTH"

Git-конвенции

Git — журнал решений. Commit message несёт всё что не хранится в файле: кто запросил, почему, что затрагивает.

конвенции commit и PR
# Commit message — полный формат [AUTH-REG-020][Additive] Регистрация через Google OAuth Requested-by: Maria K. / Product Reason: OAuth initiative Q2 2024 Decision: D-001 (email от Google = verified автоматически) Stakeholders: Maria K. (Product), Ivan P. (CTO) Breaking: false Affects: AUTH-MERGE-001, AUTH-LOG-020 # Именование веток feat/AUTH-REG-020_google-oauth-registration ← новая фича amend/AUTH-OTP-002a_retry-timeout ← правка rfc/AUTH-boundary-extract-otp ← RFC domain/AUTH-model-identity-aggregate ← ModelChange # PR title [AUTH-REG-020][Additive] feat: Google OAuth registration # PR description — обязательные блоки ## Spec change ## Verified by (чеклист стейкхолдеров) ## Open Questions resolved ## Breaking changes ## Affects

Три перспективы на один репозиторий

Одни и те же файлы дают разные срезы для разных ролей. Ключевой инструмент — git diff между нужными тегами или ветками.

РольСрезИнструмент
АналитикАктуальные требования + открытые вопросыmain ветка + файлы в _draft/
РазработчикДельта требований в своей веткеgit diff main feat/...
ТестировщикЧто изменилось в сценариях релизаgit diff release/v1..release/v2
РуководительПолная картина + риски_index.md + grep-запросы

Принципы внесения изменений

1. Атом не удаляется — он устаревает. Файл переезжает в _deprecated/ с указанием deprecated-by и причины. Удалённый атом разрывает историю.

2. Листья добавляются, ветви изменяются минимально. Новый сценарий = новый файл. Изменение use-case = diff с changelog в commit. Чем выше уровень, тем консервативнее.

3. Scope атома не расширяется — создаётся новый. Расширение scope = скрытое изменение контракта. Все кто ссылается на атом молча получают другую семантику.

4. Domain Rules — только append, никогда rewrite. Изменение DR записывается с явной пометкой: [updated: 2024-03, reason: security policy v3].

5. Открытые вопросы блокируют реализацию, не документацию. Атом с открытыми OQ существует и виден. Команда знает что вопрос есть, не додумывает сама.

6. Нет изменения домена без явного решения. ModelChange и BoundaryChange требуют § Decision Log с альтернативами и обоснованием.

7. PR-граница = доменная граница риска. Не смешивать в один PR: доменные изменения + новые сценарии, high-risk + low-risk, изменение домена + изменение платформы.

Эволюция домена — живой пример

Пример: система аутентификации прошла 4 итерации. Вот как менялось дерево и что это значило для команды.

эволюция домена AUTH — сводная таблица
Iter 0 Iter 1 Iter 2 Iter 3 email/pass phone/OTP +Google +GitHub ───────────────────────────────────────────────────────────────────── domain.spec v1.0 v2.0 Breaking v3.0 Break v3.1 Add AUTH-REG-001 active deprecated AUTH-REG-010 active active active AUTH-REG-020 active active AUTH-REG-030 active AUTH-MERGE-001 _draft→ active ───────────────────────────────────────────────────────────────────── Всего атомов 7 14 21 30 Живых 7 8 15 24 Deprecated 0 6 6 6 # Вывод: добавление GitHub (iter 3) обошлось в 3x дешевле Google (iter 2) # потому что Identity-агрегат в v3.0 — это инвестиция в расширяемость
Обучающие кейсы

Кейс: Интегратор платёжных систем

Контекст: продукт интегрирует несколько платёжных провайдеров — Stripe (международные карты), Tinkoff (RU-карты), СБП (QR-код). Команда: 1 PM, 3 разработчика, 1 QA. Спринты двухнедельные.

Главная сложность: у каждого провайдера свои правила, свои webhook-форматы, своя логика возврата. При добавлении нового провайдера нельзя сломать существующие. Доменная модель должна быть независимой от провайдера.

Доменная модель

specs/payment/domain.spec.md v2.1
## § Domain Model Aggregate: Payment id: UUID orderId: UUID → Order amount: Money { value: Decimal, currency: ISO4217 } provider: "stripe" | "tinkoff" | "sbp" status: pending | authorized | captured | failed | refunded providerRef: string ← внешний ID в системе провайдера metadata: map ← provider-specific данные Aggregate: Refund id: UUID paymentId: UUID → Payment amount: Money reason: string status: pending | processed | failed Events: PaymentInitiated { paymentId, orderId, provider, amount } PaymentAuthorized { paymentId, providerRef } PaymentCaptured { paymentId } PaymentFailed { paymentId, reason, providerCode } RefundRequested { refundId, paymentId, amount } RefundProcessed { refundId } ## § Decision Log D-001: "Money как Value Object, не число" rationale: "Исключает ошибки с валютой и округлением" D-002: "providerRef — строка, не типизирован" rationale: "Каждый провайдер имеет свой формат ID"

Дерево файлов

📁 specs/payment/
├── domain.spec.md v2.1 · Payment + Refund агрегаты
├── _index.md
├── 📁 initiation/
│ ├── PAY-INI-010_stripe-card-payment.spec.md
│ ├── PAY-INI-011_stripe-3ds-required.spec.md
│ ├── PAY-INI-012_stripe-card-declined.spec.md
│ ├── PAY-INI-020_tinkoff-card-payment.spec.md
│ ├── PAY-INI-030_sbp-qr-payment.spec.md new
│ ├── PAY-INI-031_sbp-qr-expired.spec.md
│ └── 📁 _deprecated/
│ └── PAY-INI-001_direct-card-storage.spec.md PCI-DSS violation, removed
├── 📁 capture/
│ ├── PAY-CAP-010_capture-authorized-payment.spec.md
│ └── PAY-CAP-011_capture-after-timeout.spec.md
├── 📁 refund/
│ ├── PAY-REF-010_full-refund.spec.md
│ ├── PAY-REF-011_partial-refund.spec.md
│ └── 📁 _draft/
│ └── PAY-REF-012_refund-after-sbp.spec.md OQ-1: СБП поддерживает частичный возврат?
└── 📁 webhook/
├── PAY-WHK-010_stripe-webhook-handler.spec.md
└── PAY-WHK-020_tinkoff-webhook-handler.spec.md

Пример атома: СБП QR-оплата

PAY-INI-030_sbp-qr-payment.spec.md
--- id: PAY-INI-030 type: use-case title: "Инициация оплаты через СБП QR-код" parent: PAYMENT children: - PAY-INI-031_sbp-qr-expired - PAY-INI-032_sbp-bank-unavailable see-also: [PAY-REF-012_refund-after-sbp] emits: [PaymentInitiated, PaymentCaptured, PaymentFailed] implementation: { status: done, sprint: Sprint-08, pr: "#412" } verification: { status: passed, blocks-release: true } --- ## § Intent Покупатель оплачивает заказ через СБП — сканирует QR-код своим банковским приложением. Оплата подтверждается через webhook от НСПК. QR действует 15 минут. ## § Domain Rules DR-1 QR-код генерируется на стороне СБП через НСПК API DR-2 QR действует 15 минут. По истечении → статус Payment: failed DR-3 Подтверждение приходит асинхронно через webhook DR-4 Двойное подтверждение одного платежа → идемпотентно игнорируется (IDMP) DR-5 СБП не поддерживает частичный capture — только полная сумма ## § Acceptance Criteria Scenario: Успешная оплата через СБП Given заказ на сумму 1500 RUB ожидает оплаты When покупатель выбирает оплату СБП Then система получает QR-код от НСПК API And QR-код отображается покупателю When покупатель сканирует QR и подтверждает в банке Then система получает webhook PaymentConfirmed от НСПК And Payment.status = captured And эмитируется PaymentCaptured { paymentId, amount: 1500 RUB } Scenario: QR истёк без оплаты [→ PAY-INI-031] Scenario: Банк покупателя недоступен [→ PAY-INI-032] ## § Platform: Web API initiate: POST /v1/payments/sbp/initiate body: { orderId: UUID, amount: Money } 201: { paymentId: UUID, qrUrl: string, expiresAt: timestamp } 422: { code: AMOUNT_TOO_SMALL } ← минимум СБП: 1 RUB webhook: POST /v1/webhooks/sbp ← описано в PAY-WHK-030

Правка в спринте: изменение таймаута QR

В Sprint-08 СБП вернул QR-таймаут 10 минут, хотя в документации НСПК было 15. После уточнения оказалось 10 минут. Правка пришла пока разработчик уже делал фичу.

PAY-INI-030a~param_sbp-qr-ttl.spec.md
--- id: PAY-INI-030a type: amendment change: ParameterChange title: "СБП QR TTL: 15 мин → 10 мин" amends: PAY-INI-030_sbp-qr-payment parameter: name: qr_ttl_minutes was: 15 becomes: 10 source: "НСПК тех.документация v3.2, раздел 4.1" conflict: status: pulled ← успели изъять из спринта resolution: "Обновить PAY-INI-030 до начала реализации" decided-by: "Alex S. / Dev, 2024-06-03" amendment-status: merged ← правка применена в основной атом ---

Эволюция: добавление СБП к Stripe + Tinkoff

Первые два провайдера хранили данные карт в Payment. При добавлении СБП оказалось что модель нужно расширить — СБП работает по-другому. Решение: ProviderSession как отдельный агрегат.

PAY~MODEL-002~model_provider-session.spec.md
--- id: PAY~MODEL-002 type: model-change change: ModelChange title: "ProviderSession агрегат для асинхронных провайдеров" affects-domain: PAYMENT affects-atoms: - PAY-INI-010 ← Stripe: добавить sessionId - PAY-INI-020 ← Tinkoff: добавить sessionId decision: id: D-003 statement: "ProviderSession отдельно от Payment" rationale: "СБП имеет свой жизненный цикл (QR → poll → confirm) независимо от Payment" alternatives: ["Добавить qrUrl поле в Payment (отклонено: God Object)"] --- ## § Model Delta + Aggregate: ProviderSession + id: UUID + paymentId: UUID → Payment + provider: "stripe" | "tinkoff" | "sbp" + sessionData: map ← qrUrl, 3dsRedirect, etc + expiresAt: timestamp? + status: active | expired | completed

Три перспективы на один спринт

РольСмотритВидит
PM_draft/refund/OQ-1 открыт: СБП возврат неизвестен → не брать в спринт
Devgit diff main feat/sbpPAY~MODEL-002 добавил ProviderSession — нужен новый репозиторий
QAgit diff release/v1..release/v2 -- specs/Новые сценарии PAY-INI-030, 031, 032 — все blocks-release: true

Кейс: Блог-платформа

Контекст: медиа-платформа для авторов. Публикации, черновики, категории, комментарии, SEO-метаданные. Начинали как простой блог — за 6 месяцев выросли в редакционную платформу с ролями.

Интересный момент: система кажется простой, но у неё нетривиальная эволюция. Начало с «публикация = статья», потом появились черновики, потом ревизии, потом совместное редактирование. Каждый шаг ломал предыдущие допущения.

Доменная модель v3.0

specs/blog/domain.spec.md v3.0
## § Domain Model Aggregate: Post id: UUID authorId: UUID → User slug: string (unique, URL-safe) status: draft | review | published | archived publishedAt: timestamp? seo: SEOMeta { title, description, ogImage } Aggregate: Revision ← добавлен в v2.0 id: UUID postId: UUID → Post content: string (Markdown) authorId: UUID → User createdAt: timestamp isCurrent: boolean Aggregate: Comment id: UUID postId: UUID → Post authorId: UUID → User content: string status: pending | approved | rejected Events: PostPublished { postId, slug, authorId } PostArchived { postId } RevisionSaved { revisionId, postId } CommentApproved { commentId, postId } ## § Changelog v1.0: Post с content:string напрямую v2.0: Breaking — content вынесен в Revision (история правок) v3.0: Additive — SEOMeta как Value Object в Post

Дерево файлов

📁 specs/blog/
├── domain.spec.md v3.0
├── 📁 publishing/
│ ├── BLOG-PUB-010_create-draft.spec.md
│ ├── BLOG-PUB-011_save-revision.spec.md
│ ├── BLOG-PUB-020_publish-post.spec.md
│ ├── BLOG-PUB-021_slug-already-taken.spec.md
│ ├── BLOG-PUB-030_archive-post.spec.md
│ └── 📁 _deprecated/
│ └── BLOG-PUB-001_publish-with-content.spec.md v1.0 era, content в Post
├── 📁 seo/
│ ├── BLOG-SEO-010_set-seo-meta.spec.md
│ └── BLOG-SEO-011_auto-generate-seo.spec.md
├── 📁 comments/
│ ├── BLOG-CMT-010_submit-comment.spec.md
│ ├── BLOG-CMT-011_moderate-comment.spec.md
│ └── 📁 _draft/
│ └── BLOG-CMT-020_nested-comments.spec.md OQ-1: глубина вложенности?
└── 📁 feed/
├── BLOG-FEED-010_get-published-posts.spec.md
└── BLOG-FEED-011_filter-by-category.spec.md

Пример атома: публикация поста

BLOG-PUB-020_publish-post.spec.md
--- id: BLOG-PUB-020 type: use-case title: "Публикация поста" parent: BLOG emits: [PostPublished] implementation: { status: done } verification: { status: passed, blocks-release: true } --- ## § Intent Автор переводит пост из статуса draft в published. После публикации пост доступен по публичному URL (slug). ## § Domain Rules DR-1 Публиковать можно только из статуса draft или review DR-2 Slug генерируется из заголовка, должен быть уникальным DR-3 Пост должен иметь хотя бы одну Revision с непустым content DR-4 SEO-метаданные не обязательны, но рекомендуются (предупреждение) DR-5 publishedAt устанавливается на момент перехода в published ## § Acceptance Criteria Scenario: Успешная публикация Given пост в статусе draft And пост имеет Revision с непустым content When автор нажимает "Опубликовать" Then Post.status = published And Post.publishedAt = now() And пост доступен по GET /posts/{slug} And эмитируется PostPublished Scenario: Slug уже занят [→ BLOG-PUB-021] Scenario: Нет контента [→ BLOG-PUB-022]

Эволюция: от простого блога к редакции

ВерсияЧто изменилосьТипЧто сломало
v1.0Post с content: string напрямуюInitial
v2.0Контент вынесен в Revision (история правок)ModelChangeВсе use-cases работавшие с Post.content
v2.1Добавлен статус review в PostRuleChangeDR-1 в BLOG-PUB-020 обновлён
v3.0SEOMeta как Value ObjectAdditiveНичего — новые поля опциональны
Ключевой урок кейса Перенос content в Revision (v2.0) — это ModelChange, который обесценил весь v1.0 код. В Atomic Spec это видно заранее: все атомы с Post.content в § Domain Model Touch перечислены в affects-atoms ModelChange-файла. Команда знала масштаб до начала разработки.

Правка: добавили обязательный SEO-title

После v3.0 аналитик сделал SEO-title обязательным при публикации. Разработчик уже делал фичу публикации.

BLOG-PUB-020b~rule_seo-title-required.spec.md
--- id: BLOG-PUB-020b type: amendment change: RuleChange title: "SEO title обязателен при публикации" amends: BLOG-PUB-020_publish-post rule: action: modify dr-id: DR-4 was: "SEO-метаданные не обязательны (предупреждение)" becomes: "SEO title обязателен для публикации. Без него — ошибка SEO_TITLE_REQUIRED" impact: [BLOG-PUB-020, BLOG-SEO-010] conflict: status: sprint-locked resolution: "Sprint-05: публикация без SEO title. Sprint-06: добавить валидацию" ---

Кейс: AI Чат-бот

Контекст: продукт — AI-ассистент для поддержки клиентов. Чаты с историей, переключение моделей (GPT-4 / Claude / Llama), контекстное окно, memory, инструменты. Команда из 4 человек. Требования меняются каждую неделю — это главный вызов для методологии.

Ключевая сложность: AI-продукты имеют нечёткие требования по умолчанию. «Бот должен отвечать умно» — не DR. Atomic Spec заставляет формализовать то, что кажется очевидным: максимальный размер контекста, поведение при ошибке модели, что считается «хорошим» ответом.

Доменная модель

specs/chat/domain.spec.md v2.0
## § Domain Model Aggregate: Conversation id: UUID userId: UUID → User model: "gpt-4" | "claude-3" | "llama-3" status: active | archived systemPrompt: string? ← кастомные инструкции tokenCount: integer ← текущий размер контекста Aggregate: Message id: UUID conversationId: UUID → Conversation role: user | assistant | system content: string tokens: integer model: string? ← модель которая генерировала (только role=assistant) latencyMs: integer? ValueObject: ContextWindow maxTokens: integer ← зависит от модели strategy: truncate-oldest | summarize Events: MessageSent { messageId, conversationId, tokens } ResponseGenerated { messageId, model, latencyMs, tokens } ContextTruncated { conversationId, removedMessages } ModelSwitched { conversationId, from, to } ## § Decision Log D-001: "tokenCount в Conversation, не в Memory" rationale: "Нужен для real-time проверки перед отправкой" D-002: "Стратегия truncate по умолчанию, не summarize" rationale: "Summarize добавляет latency и стоит токены" alternatives: ["summarize (отклонено: дорого)", "hard error (отклонено: UX)"]

Дерево файлов

📁 specs/chat/
├── domain.spec.md v2.0
├── 📁 messaging/
│ ├── CHAT-MSG-010_send-user-message.spec.md
│ ├── CHAT-MSG-011_stream-response.spec.md
│ ├── CHAT-MSG-012_model-error-fallback.spec.md
│ └── CHAT-MSG-013_context-truncation.spec.md
├── 📁 models/
│ ├── CHAT-MOD-010_switch-model.spec.md
│ ├── CHAT-MOD-011_model-unavailable.spec.md
│ └── 📁 _draft/
│ └── CHAT-MOD-020_auto-model-routing.spec.md OQ-1, OQ-2 открыты
├── 📁 history/
│ ├── CHAT-HST-010_load-conversation.spec.md
│ └── CHAT-HST-011_archive-conversation.spec.md
└── 📁 tools/
├── CHAT-TOOL-010_web-search-tool.spec.md
└── 📁 _draft/
└── CHAT-TOOL-020_code-execution-tool.spec.md OQ-3: sandbox требования

Пример атома: отправка сообщения

CHAT-MSG-010_send-user-message.spec.md
--- id: CHAT-MSG-010 type: use-case title: "Отправка сообщения пользователем" emits: [MessageSent, ResponseGenerated] see-also: [CHAT-MSG-013_context-truncation] implementation: { status: done } verification: { status: passed, blocks-release: true } --- ## § Intent Пользователь отправляет текстовое сообщение в активный чат. Система передаёт контекст разговора в LLM и стримит ответ. ## § Domain Rules DR-1 Максимальная длина одного сообщения: 4000 символов DR-2 Перед отправкой: проверить что tokenCount + newTokens <= model.maxTokens DR-3 Если контекст переполнен → выполнить truncation (CHAT-MSG-013), затем отправить DR-4 Ответ стримится чанками по мере генерации — не ждать полного ответа DR-5 Timeout ожидания первого чанка: 10 секунд. Затем → fallback (CHAT-MSG-012) DR-6 Latency каждого ответа записывается в Message.latencyMs ## § Acceptance Criteria Scenario: Успешная отправка и получение ответа Given активный чат с моделью gpt-4 And tokenCount в пределах лимита When пользователь отправляет сообщение "Как дела?" Then создаётся Message { role: user } And система начинает стримить ответ в течение 10 секунд And создаётся Message { role: assistant, model: "gpt-4" } And эмитируется ResponseGenerated с latencyMs Scenario: Сообщение слишком длинное Given пользователь вводит текст длиннее 4000 символов Then ошибка MESSAGE_TOO_LONG до отправки ## § Constraints PERF Первый чанк ответа: < 2s при p95 SEC Контент проходит через модерацию перед отправкой в LLM ## § Platform: Web API POST /v1/conversations/{id}/messages body: { content: string } response: SSE stream event: chunk { delta: string } event: done { messageId: UUID, tokens: int, latencyMs: int } event: error { code: string }

Как обращаться с быстрыми изменениями

AI-продукты — это 2-3 изменения требований в неделю. Atomic Spec для таких продуктов имеет особую практику:

СитуацияКак обработать
Изменился timeout модели~param amendment — одно поле, один верификатор, один PR
Добавили новую модель в enumAdditive в domain.spec.md — одна строка в модели
Изменилась логика truncation~rule amendment к CHAT-MSG-013 — product + tech верификация
Новый тип инструментаНовый атом в tools/ — создаётся в _draft/ пока OQ открыты
Смена LLM-провайдераModelChange — требует RFC, блокирует спринт
Правило быстрых продуктов Чем быстрее меняются требования — тем важнее типизация изменений. ParameterChange (timeout) и ModelChange (новый агрегат) должны обрабатываться по-разному. Atomic Spec делает эту разницу видимой и обязательной.

Смена LLM-модели: пример ModelChange

Команда решила добавить поддержку Llama-3 как дешёвой альтернативы. При этом у Llama другой API, другой лимит контекста, нет function calling. Это не просто «добавить строку в enum».

CHAT~MODEL-002~model_llama-provider.spec.md
--- id: CHAT~MODEL-002 type: model-change change: ModelChange title: "Поддержка Llama-3 как провайдера без function calling" affects-domain: CHAT affects-atoms: - CHAT-MSG-010 ← tools недоступны для Llama - CHAT-TOOL-010 ← web-search tool только для GPT/Claude - CHAT-MOD-010 ← переключение модели: новые ограничения decision: id: D-004 statement: "ModelCapabilities как ValueObject — не все модели одинаковы" rationale: "Llama не поддерживает tools. Нельзя это хардкодить в каждом use-case" ## § Model Delta + ValueObject: ModelCapabilities + supportsTools: boolean + supportsStreaming: boolean + maxContextTokens: integer + costPerToken: Decimal Aggregate: Conversation - model: "gpt-4" | "claude-3" | "llama-3" + model: ModelId ← отдельный тип с capabilities + capabilities: ModelCapabilities ← кэш на момент создания чата

Кейс: Интернет-магазин

Контекст: e-commerce платформа с несколькими командами. Каталог товаров, корзина, заказы, доставка, возвраты. Каждый домен — своя команда (2-3 человека). Это делает межкомандное взаимодействие ключевым вызовом.

Главная сложность: несколько доменов взаимодействуют через события. Изменение в одном домене может неожиданно сломать другой. Atomic Spec делает эти зависимости явными через emits / consumes в frontmatter.

Домены системы

system.spec.md — границы доменов
## § Domains CATALOG: Товары, категории, наличие, цены Команда: Team A (2 dev) Публикует: ProductUpdated, StockChanged CART: Корзина, применение скидок, проверка наличия Команда: Team B (2 dev) Потребляет: StockChanged, PriceUpdated Публикует: CartCheckedOut ORDER: Заказы, статусы, история Команда: Team B (тот же, 2 dev) Потребляет: CartCheckedOut, PaymentCaptured Публикует: OrderConfirmed, OrderCancelled DELIVERY: Доставка, трекинг, курьеры Команда: Team C (1 dev + интеграция) Потребляет: OrderConfirmed Публикует: DeliveryStarted, DeliveryCompleted PAYMENT: Платежи (см. кейс 1) Потребляет: OrderConfirmed Публикует: PaymentCaptured, RefundProcessed ## § Event Map (критические зависимости) CATALOG → (StockChanged) → CART CART → (CartCheckedOut) → ORDER ORDER → (OrderConfirmed) → DELIVERY, PAYMENT PAYMENT → (PaymentCaptured) → ORDER

Дерево файлов

📁 specs/
├── system.spec.md v1.2 · межкомандные зависимости
├── 📁 catalog/
│ ├── domain.spec.md v1.1
│ ├── CAT-PRD-010_add-product.spec.md
│ ├── CAT-PRD-011_update-price.spec.md
│ └── CAT-STK-010_update-stock.spec.md
├── 📁 cart/
│ ├── domain.spec.md
│ ├── CART-010_add-item.spec.md
│ ├── CART-011_remove-item.spec.md
│ ├── CART-020_apply-promo-code.spec.md
│ ├── CART-030_checkout.spec.md
│ └── CART-031_item-out-of-stock-on-checkout.spec.md
├── 📁 order/
│ ├── domain.spec.md
│ ├── ORD-010_confirm-order.spec.md
│ ├── ORD-011_cancel-order.spec.md
│ └── 📁 _draft/
│ └── ORD-020_partial-cancel.spec.md OQ-1: частичный отказ от товара?
└── 📁 delivery/
├── domain.spec.md
├── DEL-010_create-delivery.spec.md
└── DEL-011_track-delivery.spec.md

Атом: оформление заказа (checkout)

CART-030_checkout.spec.md
--- id: CART-030 type: use-case title: "Оформление заказа (checkout)" parent: CART emits: [CartCheckedOut] consumes: [StockChanged] ← слушаем изменения наличия see-also: [ORD-010_confirm-order, CART-031_item-out-of-stock] --- ## § Intent Покупатель завершает формирование корзины и переходит к оплате. Система резервирует товары и создаёт заказ в домене ORDER. ## § Domain Rules DR-1 Перед checkout: проверить актуальное наличие каждой позиции DR-2 Если товар недоступен → ошибка с указанием конкретных позиций DR-3 Цена фиксируется на момент checkout (не меняется при изменении каталога) DR-4 Резервирование через StockReservation — не вычитание из остатка DR-5 Резервирование действует 30 минут. Затем — освобождается автоматически DR-6 Минимальная сумма заказа: 100 RUB (конфигурируемый параметр) ## § Acceptance Criteria Scenario: Успешный checkout Given корзина с 2 товарами, оба в наличии When покупатель нажимает "Оформить заказ" Then цена каждого товара зафиксирована на текущем уровне And резервирование создано на 30 минут And эмитируется CartCheckedOut { cartId, items, totalPrice } And ORDER-домен создаёт Order (через событие) Scenario: Товар кончился пока шёл checkout [→ CART-031] Scenario: Сумма ниже минимума [→ CART-032] ## § Decision Log D-001: "Резервирование, не вычитание" rationale: "Позволяет освободить если оплата не прошла без двойного списания" alternatives: ["Сразу вычитать (отклонено: нельзя легко откатить)"]

BoundaryChange: выделение Inventory в отдельный домен

После роста команды управление остатками выделили в отдельный домен INVENTORY. Это BoundaryChange — самое дорогое изменение, требует RFC.

~boundary_extract-inventory-domain.rfc.md
--- id: RFC-002 type: boundary-change title: "Выделение управления остатками в домен INVENTORY" status: accepted # proposed | accepted | rejected moves: from-domain: CATALOG to-domain: INVENTORY ← новый домен, новая команда Team D atoms: - CAT-STK-010_update-stock - CAT-STK-011_reserve-stock - CAT-STK-012_release-reservation impact: - CART-030 ← consumes StockChanged — источник меняется - ORD-010 ← зависит от резервирования cross-team: true votes: - { stakeholder: "CTO", vote: accept, date: 2024-09-01 } - { stakeholder: "Arch", vote: accept, date: 2024-09-02 } - { stakeholder: "Product", vote: accept, date: 2024-09-03 } migration-plan: Sprint-20: создать INVENTORY домен, скопировать атомы Sprint-21: переключить CART/ORDER на новые события Sprint-22: deprecated CAT-STK-* атомы, финальный smoke test ---

Перспективы нескольких команд

При работе нескольких команд на одном репозитории каждая команда работает со своим поддеревом, но видит зависимости через system.spec.md.

КомандаСвоё поддеревоСледит за событиямиРиск
Team A (Catalog)specs/catalog/Публикует StockChangedИзменение формата события ломает Team B
Team B (Cart + Order)specs/cart/, specs/order/Потребляет StockChanged, публикует CartCheckedOutЗависит от двух других команд
Team C (Delivery)specs/delivery/Потребляет OrderConfirmedЛюбое изменение в ORD-010 требует проверки
Межкомандное правило Изменение emits в любом атоме — это Breaking change для всех команд у которых этот event в consumes. Перед любым изменением события: найти все атомы с этим event в consumes и уведомить их команды.
grep: найти все потребители события
# Кто потребляет StockChanged — уведомить перед изменением grep -r "StockChanged" specs/ --include="*.spec.md" -l # Вывод: specs/cart/CART-030_checkout.spec.md specs/cart/CART-031_item-out-of-stock.spec.md # → Уведомить Team B до любого изменения StockChanged
Обучающие кейсы

Методология
на реальных примерах

Четыре разных системы — один подход. Смотрите как выглядит дерево атомов, как эволюционирует домен, как команда работает с реальными требованиями.

Как читать кейс

Каждый кейс показывает полный путь

01
Доменная модель
Агрегаты, события, инварианты — с чего начинается система
02
Дерево атомов
Реальная структура файлов с use-cases и сценариями
03
Эволюция
Как система меняется от итерации к итерации
Обучающие кейсы

Обзор: интегратор платёжных систем

Система принимает платежи через несколько провайдеров: Stripe (международные карты), ЮKassa (РФ-карты), СБП (быстрые переводы). Это классический случай когда структура атомов из примера с OAuth-провайдерами применяется к платёжному домену — один абстрактный Provider, несколько конкретных реализаций.

Ключевые особенности этой системы для документирования:

  • Идемпотентность — повторный запрос не должен создавать второй платёж
  • Webhook-и — провайдер асинхронно сообщает об изменении статуса
  • Частичные отказы — платёж начался, провайдер упал в середине
  • Reconciliation — сверка с провайдером, обнаружение расхождений

Доменная модель

specs/payments/domain.spec.md — v2.1
## § Domain Model Aggregate: Payment id: UUID orderId: UUID → Order amount: Money { value, currency } provider: "stripe" | "yukassa" | "sbp" status: pending | processing | succeeded | failed | refunded idempotencyKey: string ← уникален per provider providerPaymentId: string? ← приходит от провайдера metadata: map ← provider-specific данные Aggregate: PaymentMethod id: UUID userId: UUID → User provider: string token: string ← токен провайдера, не raw данные карты type: "card" | "wallet" | "sbp" last4: string? Aggregate: Webhook id: UUID provider: string eventType: string payload: JSON processed: boolean paymentId: UUID? ← связь устанавливается при обработке ## § Domain Events PaymentInitiated { paymentId, orderId, amount, provider } PaymentSucceeded { paymentId, orderId, providerPaymentId } PaymentFailed { paymentId, orderId, reason, retryable } PaymentRefunded { paymentId, amount, reason } WebhookReceived { webhookId, provider, eventType } ReconciliationGap { paymentId, localStatus, providerStatus } ## § Invariants INV-1 Payment.amount не изменяется после создания INV-2 idempotencyKey уникален per provider — дубль = 409 INV-3 succeeded → refunded — единственный допустимый переход назад INV-4 Webhook обрабатывается ровно один раз (exactly-once)

Дерево атомов

📁 specs/payments/
├── _index.md
├── domain.spec.md v2.1
├── 📁 _draft/
│ └── PAY-REC-001_reconciliation-gap-resolution.spec.md OQ-1 открыт
├── 📁 _deprecated/
│ └── PAY-CHG-001_direct-card-charge.spec.md заменён токенизацией
├── 📁 charge/
│ ├── PAY-CHG-010_initiate-payment.spec.md
│ ├── PAY-CHG-011_duplicate-idempotency-key.spec.md
│ ├── PAY-CHG-012_provider-unavailable.spec.md
│ ├── PAY-CHG-013_insufficient-funds.spec.md
│ ├── PAY-CHG-020_stripe-charge.spec.md
│ ├── PAY-CHG-030_yukassa-charge.spec.md
│ └── PAY-CHG-040_sbp-charge.spec.md
├── 📁 webhook/
│ ├── PAY-WH-010_webhook-received.spec.md
│ ├── PAY-WH-011_duplicate-webhook.spec.md
│ ├── PAY-WH-012_unknown-payment-webhook.spec.md
│ └── PAY-WH-013_webhook-signature-invalid.spec.md
├── 📁 refund/
│ ├── PAY-REF-010_initiate-refund.spec.md
│ └── PAY-REF-011_refund-already-refunded.spec.md
└── 📁 reconciliation/
└── PAY-REC-010_daily-reconciliation.spec.md

Ключевой атом: инициация платежа

PAY-CHG-010_initiate-payment.spec.md
--- id: PAY-CHG-010 type: use-case title: "Инициация платежа через провайдера" parent: PAYMENTS children: - PAY-CHG-011_duplicate-idempotency-key - PAY-CHG-012_provider-unavailable - PAY-CHG-013_insufficient-funds emits: [PaymentInitiated, PaymentSucceeded, PaymentFailed] see-also: [PAY-WH-010] implementation: { status: done } verification: { status: passed, blocks-release: true } --- ## § Intent Система инициирует платёж через выбранного провайдера. Успех или отказ приходит либо синхронно, либо через webhook. ## § Domain Rules DR-1 idempotencyKey = SHA256(orderId + amount + currency + provider) DR-2 Повторный запрос с тем же idempotencyKey → 200 с существующим платежом, не новый DR-3 Провайдер выбирается по currency: RUB → ЮKassa/СБП, остальные → Stripe DR-4 Timeout ожидания ответа провайдера: 30 секунд DR-5 После timeout → status: failed, retryable: true, эмитится PaymentFailed ## § Acceptance Criteria Scenario: Успешный платёж через Stripe Given заказ ORDER-123 на сумму 50 USD And выбран провайдер Stripe When система инициирует платёж Then создаётся Payment { status: processing } And эмитится PaymentInitiated And Stripe возвращает paymentIntentId When приходит webhook payment_intent.succeeded Then Payment.status → succeeded And эмитится PaymentSucceeded ## § Platform: Web API contract: method: POST path: /v1/payments body: orderId: UUID amount: { value: number, currency: string } provider: string? # если не указан — выбирается автоматически methodId: UUID? # сохранённый PaymentMethod responses: 201: { paymentId, status: "processing", providerData } 200: { paymentId, status, ... } # idempotency hit 402: { code: INSUFFICIENT_FUNDS } 503: { code: PROVIDER_UNAVAILABLE, retryAfter }

Атом: обработка webhook

PAY-WH-010_webhook-received.spec.md
## § Domain Rules DR-W-1 Webhook проверяется по HMAC-подписи провайдера до обработки DR-W-2 Один webhook обрабатывается ровно один раз (exactly-once через Webhook.processed) DR-W-3 Повторный webhook с тем же eventId → 200, обработка не запускается DR-W-4 Неизвестный paymentId в webhook → Webhook сохраняется, статус: orphaned DR-W-5 Обработка асинхронна — HTTP 200 возвращается немедленно, обработка в очереди ## § Acceptance Criteria Scenario: Stripe сообщает об успешном платеже Given Payment { id: P-1, status: processing } When приходит webhook { type: payment_intent.succeeded, paymentIntentId: ... } Then HTTP 200 возвращается немедленно And асинхронно: Payment.status → succeeded And эмитится PaymentSucceeded Scenario: Дублирующий webhook → PAY-WH-011 Scenario: Невалидная подпись → PAY-WH-013

Идемпотентность как доменное правило

Идемпотентность — не техническая деталь, а бизнес-правило. Оно живёт в доменных правилах use-case, а не в платформенной секции. Это позволяет тестировщику проверять его независимо от реализации.

PAY-CHG-011_duplicate-idempotency-key.spec.md
--- id: PAY-CHG-011 type: scenario parent: PAY-CHG-010 dr-covers: [DR-2] --- ## § Acceptance Criteria Scenario: Повторный запрос с тем же idempotencyKey Given существует Payment { idempotencyKey: "key-abc", status: processing } When приходит новый запрос с тем же orderId + amount + currency + provider Then HTTP 200 (не 201) And возвращается существующий paymentId And новый Payment НЕ создаётся And провайдер НЕ вызывается повторно ## § Platform Tests // @spec PAY-CHG-011 it('200 idempotency — не создаёт второй платёж', async () => { const first = await api.post('/v1/payments', payload) const second = await api.post('/v1/payments', payload) expect(second.status).toBe(200) expect(second.body.paymentId).toBe(first.body.paymentId) expect(await db.payments.count({ orderId })).toBe(1) })

Итерация 1 → 2 → 3: добавление провайдеров

ИтерацияЧто добавляемИзменение доменаНовых атомов
v1.0 — StripeБазовый платёж USD/EURdomain v1.0 — базовая модель7 атомов
v2.0 — ЮKassaRUB карты, двухэтапное подтверждениеdomain v2.0 — добавлен Capture+5 атомов
v2.1 — СБПQR-код, push-уведомление, 24ч timeoutdomain v2.1 — Additive, QR flow+4 атома
Паттерн тот же что с OAuth Первый провайдер — строим модель. Второй — рефакторим к абстракции (Payment.provider). Третий — чистое расширение enum. Инвестиция в абстракцию на v2.0 делает v2.1 почти бесплатным.

Правка в спринте: изменение timeout

PAY-CHG-010a~param_provider-timeout.spec.md
--- id: PAY-CHG-010a type: amendment change: ParameterChange title: "Timeout провайдера: 30с → 10с" amends: PAY-CHG-010_initiate-payment parameter: name: provider_timeout_seconds was: 30 becomes: 10 requested-by: stakeholder: "DevOps / SRE" reason: "30с держит DB connection, cascade timeout в 23:00 traffic spike" conflict: status: sprint-locked locked-sprint: "Sprint-8" resolution: "Sprint-8 завершает с 30с. Amendment → Sprint-9, отдельный PR" ---

Обзор: блог-платформа

Кажется простой системой — пока не начнёшь разбираться в деталях. Черновики, публикация, редактирование опубликованного, версии, модерация комментариев, SEO-метаданные, теги — каждая из этих фич содержит неочевидные доменные решения. Этот кейс показывает как расти от MVP к полноценной платформе без переписывания атомов.

Ключевые вопросы которые возникнут:

  • Черновик и публикация — один агрегат или два?
  • Редактирование опубликованного — создаёт новую версию или меняет существующую?
  • Удалённый пост с комментариями — что происходит с комментариями?
  • Тег удалён — посты с ним остаются?

Доменная модель (итог всех итераций)

specs/blog/domain.spec.md — v3.0
## § Domain Model Aggregate: Post id: UUID authorId: UUID → User slug: string (unique) ← URL-идентификатор status: draft | published | archived currentRevisionId: UUID → PostRevision tagIds: UUID[] publishedAt: timestamp? Aggregate: PostRevision ← добавлен в v3.0 id: UUID postId: UUID → Post title: string body: string (markdown) seoMeta: { title?, description?, ogImage? } createdAt: timestamp createdBy: UUID → User Aggregate: Comment id: UUID postId: UUID → Post authorId: UUID → User parentId: UUID? → Comment ← для вложенности body: string status: pending | approved | rejected Aggregate: Tag id: UUID slug: string (unique) name: string ## § Invariants INV-1 Post.slug не изменяется после публикации (SEO) INV-2 Редактирование опубликованного → создаёт PostRevision, не изменяет текущую INV-3 Удаление Post → Comment.status = archived (не удаляются) INV-4 Tag удаляется только если postIds пуст (нет привязанных постов) ## § Decision Log D-001 Черновик = Post { status: draft }, не отдельный агрегат Rationale: один агрегат — одна история, проще работа с переходами статусов D-002 Редактирование → новая RevisionList, не мутация Post.body Rationale: история изменений, возможность rollback, аудит

Дерево атомов — финальное состояние

📁 specs/blog/
├── domain.spec.md v3.0
├── 📁 _deprecated/
│ └── BLG-PST-001_simple-publish.spec.md MVP, заменён BLG-PST-010
├── 📁 posts/
│ ├── BLG-PST-010_create-draft.spec.md
│ ├── BLG-PST-011_publish-post.spec.md
│ ├── BLG-PST-012_edit-published-post.spec.md создаёт ревизию
│ ├── BLG-PST-013_archive-post.spec.md
│ ├── BLG-PST-014_slug-already-taken.spec.md
│ └── BLG-PST-015_rollback-to-revision.spec.md
├── 📁 comments/
│ ├── BLG-CMT-010_submit-comment.spec.md
│ ├── BLG-CMT-011_moderate-comment.spec.md
│ └── BLG-CMT-012_comment-on-deleted-post.spec.md
├── 📁 tags/
│ ├── BLG-TAG-010_create-tag.spec.md
│ └── BLG-TAG-011_delete-tag-with-posts.spec.md
└── 📁 seo/
├── BLG-SEO-010_post-seo-metadata.spec.md
└── BLG-SEO-011_sitemap-generation.spec.md

Итерация 1 — MVP: просто опубликовать

В MVP нет черновиков, нет версий, нет тегов. Просто написать и опубликовать.

Итерация 1 — domain v1.0, 3 атома
domain.spec.md v1.0 Aggregate: Post id, authorId, title, body, slug, status: draft|published BLG-PST-001_simple-publish.spec.md ← это потом deprecated BLG-PST-002_duplicate-slug.spec.md BLG-PST-003_publish-success.spec.md Decision Log: D-001 Нет отдельного Draft агрегата — Post.status = draft достаточно для MVP Rationale: YAGNI, добавим версионирование когда понадобится редактирование

Итерация 2 — Комментарии

Добавляем комментарии. Возникает первый неочевидный вопрос: что происходит с комментариями при удалении поста?

BLG-CMT-012_comment-on-deleted-post.spec.md
--- id: BLG-CMT-012 type: scenario parent: BLG-CMT-010 --- ## § Open Questions OQ-1 [resolved] Удалять комментарии каскадно или архивировать? Resolution: архивировать — потеря пользовательского контента недопустима Resolved-by: Product, 2024-03-10 ## § Acceptance Criteria Scenario: Попытка комментировать удалённый/архивированный пост Given Post { status: archived } When пользователь отправляет новый комментарий Then HTTP 422 { code: POST_NOT_AVAILABLE } And существующие комментарии НЕ удаляются (status: archived) And новый Comment НЕ создаётся ## § Decision Log D-003 Каскадное удаление комментариев при архивации поста запрещено Rationale: пользователи тратили время на контент, удаление без согласия неэтично Decided-by: Product + Legal, 2024-03-10

Итерация 3 — Теги и SEO

Теги — простая фича с неочевидным краевым случаем: что если удалить тег у которого есть посты?

Важное доменное решение INV-4 фиксирует: Tag удаляется только если нет привязанных постов. Это не техническое ограничение — это бизнес-решение. Оно должно жить в domain.spec.md, а не в коде валидации.

Итерация 4 — Версионирование редактирования

Это Breaking изменение — добавляется агрегат PostRevision. Меняется модель Post.

domain v2.0domain v3.0
Post.bodystring — хранит текст напрямуюудалено — текст в PostRevision
Post.titlestringудалено — в PostRevision
PostRevisionнетновый агрегат
Редактированиемутирует Postсоздаёт PostRevision
ModelChange — отдельный PR до начала спринта BLG~MODEL-001~model_post-revision-aggregate.spec.md должен быть смержен и верифицирован до того как разработчик начнёт реализацию BLG-PST-012 и BLG-PST-015. Иначе — конфликт в середине спринта.

Роли в системе и их пересечения

РольМожетНе может
Authorсоздать, редактировать свои посты, публиковатьмодерировать чужие комментарии, удалять теги
Editorвсё что Author + редактировать любые постыудалять пользователей, управлять тегами
Adminвсё
Readerчитать, комментироватьсоздавать посты

Каждое ролевое разграничение — отдельный Domain Rule в соответствующем use-case. Не в коде middleware, не в README — в атоме.

Обзор: ИИ-чат бот

Самый нестандартный кейс для Atomic Spec: поведение системы нетерминировано. Один и тот же ввод может дать разный вывод. Как писать требования и тесты для такой системы?

Ответ: документируем не ожидаемый точный ответ, а ожидаемое поведение — классификацию намерения, наличие tool call, структуру ответа, границы допустимого. Acceptance Criteria работают со свойствами ответа, а не с его точным содержанием.

Вызов: нетерминизм в требованиях

Как НЕ надо писать сценарий для ИИ
✗ ПЛОХО — тестирует точный текст, хрупко Scenario: Пользователь спрашивает о погоде Given сообщение "какая погода в Москве?" Then ответ содержит "Москва" и температуру в °C And текст ответа: "В Москве сейчас 15 градусов" ← ХРУПКО ✓ ХОРОШО — тестирует поведение и структуру Scenario: Пользователь спрашивает о погоде Given сообщение содержит намерение WEATHER_QUERY с location=Москва When бот обрабатывает сообщение Then вызывается tool: get_weather { city: "Москва" } And ответ содержит числовое значение температуры And ответ НЕ содержит disclaimer об отсутствии real-time данных And тон ответа: информативный, не алармистский

Доменная модель

specs/chatbot/domain.spec.md — v2.0
## § Domain Model Aggregate: Conversation id: UUID userId: UUID → User status: active | archived | escalated model: string ← "claude-3", "gpt-4" и т.д. systemPromptId: UUID → SystemPrompt tokenCount: number ← сумма всех сообщений Aggregate: Message id: UUID conversationId: UUID → Conversation role: "user" | "assistant" | "tool" content: string | ContentBlock[] toolCalls: ToolCall[]? intent: string? ← классифицируется при сохранении tokens: number Aggregate: SystemPrompt id: UUID version: semver ← промпты версионируются как код content: string scope: "global" | "persona" | "tool-specific" ## § Invariants INV-1 Conversation.tokenCount не превышает model.contextWindow INV-2 При достижении 80% контекста — создаётся Summary и старые сообщения архивируются INV-3 Escalated conversation не получает новых AI-ответов — только human agent INV-4 Tool call без результата не отправляется в следующий запрос к LLM ## § Domain Events MessageReceived { conversationId, messageId, intent } BotResponded { conversationId, messageId, toolCallsCount } ConversationEscalated { conversationId, reason, triggeredBy } ContextCompressed { conversationId, summaryCreated, messagesArchived }

Дерево атомов

📁 specs/chatbot/
├── domain.spec.md v2.0
├── 📁 _draft/
│ └── BOT-ESC-020_auto-escalation-rules.spec.md OQ-2 открыт
├── 📁 conversation/
│ ├── BOT-CNV-010_start-conversation.spec.md
│ ├── BOT-CNV-011_send-message.spec.md
│ ├── BOT-CNV-012_context-window-overflow.spec.md
│ └── BOT-CNV-013_archive-conversation.spec.md
├── 📁 tools/
│ ├── BOT-TOOL-010_weather-tool-call.spec.md
│ ├── BOT-TOOL-011_tool-call-failure.spec.md
│ └── BOT-TOOL-012_tool-call-timeout.spec.md
├── 📁 fallback/
│ ├── BOT-FLB-010_llm-unavailable.spec.md
│ ├── BOT-FLB-011_content-policy-violation.spec.md
│ └── BOT-FLB-012_rate-limit-exceeded.spec.md
└── 📁 escalation/
├── BOT-ESC-010_manual-escalation.spec.md
└── BOT-ESC-011_escalation-to-human.spec.md

Ключевой атом: отправка сообщения

BOT-CNV-011_send-message.spec.md
## § Domain Rules DR-1 Сообщение пользователя сохраняется до отправки в LLM DR-2 История передаётся целиком, если tokenCount < 80% contextWindow DR-3 При tokenCount > 80% — сначала компрессия (BOT-CNV-012), затем запрос DR-4 Ответ ассистента сохраняется независимо от наличия tool calls DR-5 Если LLM недоступен — пользователь получает fallback сообщение (BOT-FLB-010) ## § Acceptance Criteria Scenario: Обычный текстовый ответ без tool calls Given активный диалог с 5 сообщениями And tokenCount < 80% contextWindow When пользователь отправляет текстовое сообщение Then LLM получает всю историю + новое сообщение And ответ ассистента сохраняется в Message { role: assistant } And эмитится BotResponded { toolCallsCount: 0 } And ответ содержит непустой текст And ответ НЕ содержит сырой JSON или технические артефакты Scenario: Запрос требует tool call Given намерение пользователя классифицировано как TOOL_REQUIRED When LLM возвращает tool_call { name, arguments } Then tool вызывается с переданными аргументами And результат добавляется в историю как Message { role: tool } And LLM вызывается повторно с результатом tool And финальный ответ пользователю — текст, не raw tool result

Как тестировать нетерминированное поведение

Три уровня тестирования ИИ-системы — каждый отвечает на свой вопрос:

УровеньЧто тестируемКак фиксируем в атоме
СтруктурныйПравильная последовательность вызовов: сохранить → LLM → сохранить ответОбычный Gherkin с Then-шагами на порядок действий
ПоведенческийНаличие/отсутствие tool call, тип intent, структура ответаThen: ответ содержит/не содержит X, tool был/не был вызван
КачественныйТон, точность, соответствие системному промпту§ Quality Criteria — отдельная секция, не Gherkin
§ Quality Criteria — специальная секция для ИИ-атомов
## § Quality Criteria # Эта секция описывает ожидания которые нельзя выразить в детерминированном тесте # Оценивается через LLM-as-judge или ручную экспертизу на выборке QC-1 Ответ на вопрос о погоде должен быть информативным, не алармистским QC-2 Бот не должен извиняться без необходимости (не более 1 раза за диалог) QC-3 При отсутствии данных — честно сообщать об этом, не галлюцинировать QC-4 Длина ответа соответствует сложности вопроса (короткий вопрос → короткий ответ) # Метод проверки evaluation-method: llm-judge judge-prompt: prompts/quality-judge-v2.md pass-threshold: 0.85 # 85% сообщений в выборке должны пройти оценку sample-size: 100

Fallback-сценарии — критически важны

BOT-FLB-010_llm-unavailable.spec.md
## § Domain Rules DR-F-1 LLM недоступен → пользователь получает human-friendly сообщение в течение 3с DR-F-2 Сообщение пользователя сохраняется даже при недоступности LLM DR-F-3 После восстановления LLM — можно ответить на сохранённое сообщение вручную DR-F-4 Если LLM недоступен > 5 минут → Conversation.status = escalated ## § Acceptance Criteria Scenario: LLM вернул 503 Given LLM API возвращает 503 When пользователь отправляет сообщение Then сообщение сохранено в базе And пользователь получает fallback-ответ в течение 3 секунд And fallback-ответ не раскрывает технические детали ошибки And в fallback-ответе есть информация о том что запрос сохранён

Эволюция: от простого чат-бота к агенту

ИтерацияЧто добавляемИзменение домена
v1.0 — Простой Q&AВопрос-ответ без памятиConversation без истории
v1.1 — ИсторияКонтекст предыдущих сообщенийAdditive: Message history
v2.0 — Tool callsПоиск погоды, базы знанийBreaking: Message.toolCalls
v2.1 — ЭскалацияПередача живому операторуAdditive: escalated status
v3.0 — АгентМногошаговые задачи, планированиеBreaking: Task aggregate

Обзор: интернет-магазин

Самый сложный кейс по числу доменов и их взаимодействию. Интернет-магазин — это несколько независимых Bounded Context, которые взаимодействуют через события, а не прямые вызовы. Atomic Spec здесь решает важнейшую проблему: кто владеет каким требованием и как изменение в одном домене влияет на другой.

Bounded Contexts — структура доменов

specs/ — верхний уровень структуры
📁 specs/ ├── 📁 catalog/ ← каталог товаров, цены, остатки ├── 📁 cart/ ← корзина, временное состояние ├── 📁 orders/ ← заказы, их жизненный цикл ├── 📁 payments/ ← (см. кейс Платёжный интегратор) ├── 📁 delivery/ ← логистика, статусы доставки ├── 📁 notifications/ ← email/sms/push по событиям └── 📁 users/ ← аккаунты, адреса, предпочтения # Каждый домен — отдельная команда или компонент # Взаимодействие — только через доменные события # orders/ не вызывает payments/ напрямую # orders/ эмитит OrderConfirmed → payments/ реагирует

Домен: каталог

specs/catalog/domain.spec.md
Aggregate: Product id: UUID sku: string (unique) status: active | discontinued | out-of-stock price: Money stock: number ← резервируется при добавлении в корзину Aggregate: PriceHistory ← история изменений цены для аудита и отображения "было/стало" ## § Invariants INV-1 Product.stock не уходит в минус — резервирование запрещено при stock=0 INV-2 Цена фиксируется в Cart.item на момент добавления — не меняется при изменении Product.price INV-3 discontinued → нельзя добавить в корзину, но существующие заказы действительны ## § Events emitted (consumed by other domains) ProductOutOfStock → cart/ помечает item как unavailable PriceChanged → notifications/ уведомляет wishlist-юзеров StockReserved { productId, quantity, cartId } StockReleased { productId, quantity, reason: "cart_expired|order_cancelled" }

Домен: корзина

specs/cart/domain.spec.md + ключевые атомы
Aggregate: Cart id: UUID userId: UUID? → User ← null для анонимных items: CartItem[] expiresAt: timestamp ← корзина живёт 30 дней status: active | checked-out | expired Entity: CartItem productId: UUID → Product quantity: number priceSnapshot: Money ← цена на момент добавления (INV-2) status: available | unavailable | price-changed ## § Ключевые use-cases CART-ADD-010 Добавить товар в корзину CART-ADD-011 Товар закончился в момент добавления ← race condition CART-ADD-012 Товар уже в корзине — увеличить количество CART-CHK-010 Начать оформление заказа CART-CHK-011 Товар стал недоступен за время оформления ← важный edge case CART-MRG-010 Слияние анонимной корзины после логина
Критический edge case: товар кончился в процессе оформления CART-CHK-011 — пользователь начал оформление, и пока он вводил адрес, последний товар купил другой. Без явного атома этот кейс обнаруживается на продакшне. Atomic Spec заставляет его зафиксировать до реализации.

Домен: заказ

specs/orders/domain.spec.md
Aggregate: Order id: UUID userId: UUID → User items: OrderItem[] ← snapshot из Cart, не ссылки status: pending → confirmed → paid → shipped → delivered | cancelled totalAmount: Money deliveryAddress: Address ← snapshot, не ссылка на User.address ## § Invariants INV-1 Order.items = snapshot из Cart — изменение Product не влияет на заказ INV-2 Order.deliveryAddress = snapshot — изменение User.address не влияет INV-3 Отмена заказа в статусе shipped только через возврат (Return flow) INV-4 cancelled → эмитится StockReleased для каждого OrderItem ## § Events OrderCreated → payments/ инициирует платёж OrderConfirmed → delivery/ создаёт задачу доставки → notifications/ шлёт подтверждение OrderCancelled → payments/ инициирует возврат → catalog/ освобождает резерв

Saga: полный путь заказа

Saga — это атом уровня System который описывает межсервисное взаимодействие. Это не use-case одного домена — это оркестрация нескольких.

specs/system/SHOP-SAGA-001_order-checkout-flow.spec.md
--- id: SHOP-SAGA-001 type: saga ← новый тип для межсервисных flows title: "Полный цикл оформления и выполнения заказа" spans: [cart, orders, payments, catalog, delivery, notifications] --- ## § Happy Path Step 1 cart/ Пользователь оформляет корзину → CartCheckedOut { cartId, items, userId } Step 2 orders/ Создаётся Order { status: pending } → OrderCreated { orderId, amount } Step 3 payments/ Инициируется платёж → PaymentSucceeded { paymentId, orderId } Step 4 orders/ Order.status → confirmed → OrderConfirmed { orderId } Step 5 delivery/ Создаётся задача доставки → DeliveryScheduled { deliveryId, orderId } Step 6 notifications/ Email + push пользователю ## § Compensation (при сбое) Если PaymentFailed: orders/ → Order.status = cancelled catalog/ → StockReleased для каждого item cart/ → Cart.status = active (можно попробовать снова) Если DeliveryFailed (нет курьера): delivery/ → OrderConfirmed остаётся, ретрай через 1ч orders/ → статус не меняется, только delivery retry ## § See Also PAY-CHG-010 инициация платежа ORD-CRT-010 создание заказа DLV-SCH-010 планирование доставки

Эволюция от MVP к полной системе

ИтерацияЧто строимДоменыКлючевые решения
MVP v1.0Каталог + Заказ + Оплата (Stripe)catalog, orders, paymentsНет корзины — прямой переход к заказу
v1.1Корзина+cartSnapshot цены в CartItem — INV-2
v2.0Доставка+deliveryBreaking: OrderConfirmed теперь нужен delivery
v2.1Уведомления+notificationsAdditive: подписка на события других доменов
v3.0Возвратыorders расширенBreaking: новый Return aggregate, Saga компенсации
Главный урок этого кейса Snapshot-паттерн (Order.items, Order.deliveryAddress, CartItem.priceSnapshot) — это доменные инварианты, не технические детали. Они должны быть явно зафиксированы в domain.spec.md. Без этого разработчик использует ссылки вместо снапшотов, и через полгода цены в старых заказах начинают меняться.

Почему Atomic Spec идеален для работы с AI

AI-агенты (Claude Code, Cursor, Copilot Workspace и другие) работают эффективно когда контекст структурирован, однозначен и предсказуем. Atomic Spec решает три ключевые проблемы агентной разработки:

Проблема агентаКак Atomic Spec решает
«Откуда мне знать что именно реализовывать?»Атом содержит § Platform Contract — точный API-контракт без интерпретации
«Это моё изменение ничего не ломает?»emits/consumes явно показывают зависимости между доменами
«Правильно ли я понял бизнес-правило?»§ Domain Rules — формализованные DR-N, не prose
«Тесты уже написаны?»§ Platform Tests — готовые тест-кейсы, агент только запускает
«Как мне обновить статус задачи?»Frontmatter с явными полями implementation.status
Ключевой принцип Агент никогда не должен додумывать требования. Если информации нет в атоме — агент создаёт Open Question, а не принимает решение самостоятельно. Atomic Spec делает границу знания явной.

Как агент читает атомы

Агент работает с атомами по чёткому протоколу. Порядок чтения соответствует секциям атома — от абстрактного к конкретному:

протокол чтения атома агентом
# Шаг 1 — прочитать frontmatter # → проверить status (draft? → СТОП, не реализовывать) # → проверить open-questions (есть open? → СТОП, создать OQ-issue) # → прочитать parent, children, see-also — понять контекст # → прочитать emits/consumes — понять зависимости # Шаг 2 — прочитать § Domain Rules # → каждый DR-N — это ограничение которое НЕЛЬЗЯ нарушить # → DR с [updated] пометкой — читать новое значение, не старое # Шаг 3 — прочитать § Acceptance Criteria # → Gherkin-сценарии = спецификация поведения # → ссылки [→ ATOM-ID] → прочитать дочерние атомы # Шаг 4 — прочитать § Platform: {target} # → выбрать нужную платформу (web-api / mobile / ...) # → контракт — источник истины для сигнатур методов # Шаг 5 — прочитать § Platform Tests # → готовые тест-кейсы — скопировать и адаптировать # → @spec комментарий обязателен в каждом тесте # Шаг 6 — обновить frontmatter # → implementation.status: none → in-progress # → зафиксировать в отдельном commit

Контекст который получает агент перед задачей

Перед тем как агент начинает реализацию, ему передаётся минимальный достаточный контекст. Слишком много — агент теряет фокус, слишком мало — додумывает.

минимальный контекст для агента
# 1. Целевой атом cat specs/auth/registration/AUTH-REG-020_google-oauth-registration.spec.md # 2. Родительский домен cat specs/auth/domain.spec.md # 3. Дочерние атомы (сценарии) cat specs/auth/registration/AUTH-REG-021_google-provider-error.spec.md # 4. Связанные атомы (see-also) cat specs/auth/identity/AUTH-MERGE-001_oauth-phone-account-linking.spec.md # 5. Проверить наличие правок find specs/ -name "AUTH-REG-020*~*" ! -path "*/_deprecated/*" # 6. История изменений атома git log --follow specs/auth/registration/AUTH-REG-020.spec.md
Правило минимального контекста Агенту нужны: целевой атом + domain.spec.md + все children + все see-also + правки. Не нужны: deprecated атомы, атомы из других доменов без явной связи, git история других файлов.

Агент создаёт новый атом

Когда агент получает задачу «добавить фичу X» без готового атома, он должен сначала создать атом, а не писать код. Порядок строгий:

алгоритм агента при создании атома
# 1. Определить место в иерархии # → какой домен? читать domain.spec.md # → какой тип (use-case / scenario)? # → какой parent? # → нет ли уже похожего атома? find specs/ -name "*.spec.md" | xargs grep -l "google oauth" -i # 2. Сгенерировать ID # → домен + тип + следующий свободный номер (шаг 10) # → проверить что ID не занят ls specs/auth/registration/ | grep AUTH-REG | sort # 3. Создать файл в _draft/ # → ВСЕГДА начинать в _draft/ — не в корне # → заполнить frontmatter полностью # → зафиксировать все неизвестности как OQ-N # 4. Заполнить секции последовательно # § Intent → § Domain Rules → § Acceptance Criteria # → НЕ писать код пока атом не готов # 5. Commit атома отдельно от кода git add specs/auth/registration/_draft/AUTH-REG-020.spec.md git commit -m "[AUTH-REG-020][draft] Google OAuth registration spec" # 6. Если OQ открыты → СТОП, передать человеку # 7. Если OQ нет → реализовывать

Агент обнаружил что требование изменилось

В процессе реализации агент может обнаружить что требование в атоме противоречит другому атому, или что поведение системы предполагает незафиксированное правило. Правильный алгоритм:

алгоритм агента при конфликте требований
# Ситуация: агент делает AUTH-REG-020, обнаружил что # domain.spec.md не описывает что делать с phone-юзером # НЕПРАВИЛЬНО — додумать самостоятельно: # // Наверное надо создать новый аккаунт # user = createUser(googleId) # ПРАВИЛЬНО — зафиксировать неопределённость: # 1. Добавить OQ в атом open-questions: - id: OQ-1 question: "Если email от Google совпадает с phone-аккаунтом — создать новый аккаунт или предложить merge?" status: open raised-by: "AI agent / AUTH-REG-020 implementation" raised-at: "2024-06-10" # 2. Переместить атом в _draft/ git mv specs/auth/registration/AUTH-REG-020.spec.md \ specs/auth/registration/_draft/AUTH-REG-020.spec.md # 3. Зафиксировать и передать человеку git commit -m "[AUTH-REG-020] OQ-1: email conflict behaviour undefined Blocked: needs product decision before implementation can continue" # 4. НЕ писать код для этой ветки логики до закрытия OQ

Агент на code review

Агент может автоматически проверять соответствие кода атомам. Это один из самых ценных сценариев: не ждать человека для базовой проверки.

чеклист code review агента по атому
# Для каждого изменённого файла в PR — найти связанный атом git diff --name-only HEAD~1 | xargs grep -l "@spec" 2>/dev/null # Проверки которые агент делает автоматически: # ✓ 1. Все DR-N реализованы? # → читать § Domain Rules, проверить каждый DR в коде # ✓ 2. Platform contract соответствует? # → HTTP статусы, поля body, коды ошибок — всё совпадает с § Platform? # ✓ 3. Все события эмитируются? # → emits: [UserRegistered] → в коде есть emit('UserRegistered', ...)? # ✓ 4. @spec комментарий в тестах? grep -r "@spec" src/__tests__/ | grep -v "AUTH-REG-020" # → тест без @spec = тест без трассировки = нарушение # ✓ 5. implementation.status обновлён? grep "implementation.status: in-progress" specs/auth/registration/AUTH-REG-020.spec.md # → должен быть in-progress или done # ✓ 6. Нет ли незакрытых правок (amendments)? find specs/ -name "AUTH-REG-020*~*" | xargs grep -l "amendment-status: pending"

Полезные запросы агента к дереву

grep-арсенал агента
# Что мне реализовывать (active, impl: none) grep -rl "implementation.status: none" specs/ \ ! -path "*/_draft/*" ! -path "*/_deprecated/*" # Есть ли правки к моей задаче find specs/ -name "AUTH-REG-020*~*" ! -path "*/_deprecated/*" # Кто потребляет событие которое я изменяю grep -r "consumes.*UserRegistered" specs/ -l # Все незакрытые OQ — что блокирует работу grep -r "status: open" specs/ --include="*.spec.md" -B2 | grep "question:" # Все атомы которые я должен обновить после ModelChange grep -r "affects-atoms" specs/ -A 10 | grep "AUTH-" # Что блокирует релиз прямо сейчас grep -rl "blocks-release: true" specs/ | \ xargs grep -l "verification.status: none" # Нет ли конфликта имён (дублирующий slug) find specs/ -name "*.spec.md" | sed 's/.*\///' | sort | uniq -d # Все атомы без @spec в тестах — пробел в трассировке diff \ <(find specs/ -name "*.spec.md" ! -path "*/_*" -exec grep -h "^id:" {} \; | awk '{print $2}' | sort) \ <(grep -r "@spec" src/__tests__/ | grep -oP 'AUTH-\w+-\d+' | sort -u)

Правила которые агент не нарушает никогда

абсолютные правила агента
ЗАПРЕЩЕНО Реализовывать атом со статусом draft или с открытыми OQ ЗАПРЕЩЕНО Удалять атомы — только deprecated ЗАПРЕЩЕНО Создавать файл в корне папки минуя _draft/ ЗАПРЕЩЕНО Изменять Domain Rules без создания RuleChange-правки ЗАПРЕЩЕНО Расширять scope атома — создавать новый ЗАПРЕЩЕНО Принимать архитектурные решения самостоятельно — только OQ ЗАПРЕЩЕНО Писать код если платформенный контракт неоднозначен ОБЯЗАТЕЛЬНО Читать domain.spec.md перед любой реализацией в домене ОБЯЗАТЕЛЬНО Проверять наличие правок (~param, ~rule, ~flow) к атому ОБЯЗАТЕЛЬНО Обновлять implementation.status при начале и окончании ОБЯЗАТЕЛЬНО Ставить @spec комментарий в каждом тесте ОБЯЗАТЕЛЬНО Проверять consumes/emits при изменении событий ОБЯЗАТЕЛЬНО Создавать OQ при любой неопределённости и останавливаться

Антипаттерны агентной разработки

АнтипаттернСимптомПравильно
Spec-blind codingАгент пишет код не прочитав атомПротокол: сначала прочитать все связанные атомы
Silent assumptionАгент заполняет пробел в требованиях своей логикойСоздать OQ, остановиться, передать человеку
Draft skipАгент создаёт атом сразу в корне папкиВсегда через _draft/, перемещение — отдельный PR
Mega commitАтом + код + тесты в одном коммитеТри отдельных коммита: spec / implementation / tests
Status driftАгент реализовал, но не обновил implementation.statusОбновление статуса — часть definition of done
Context overloadАгент читает весь specs/ перед задачейТолько целевой атом + domain + children + see-also
Rule inventionАгент добавляет DR которого нет в атомеТолько DR явно описанные аналитиком. Остальное — OQ

Системный промпт агента

Компактный промпт для AI-агента работающего с Atomic Spec репозиторием. Содержит всё необходимое — ничего лишнего.

AGENT_SYSTEM_PROMPT.md — скопировать целиком
# Atomic Spec Agent — System Prompt Ты работаешь с репозиторием использующим методологию Atomic Spec. Каждое требование — .spec.md файл. Ты читаешь атомы перед реализацией. Ты никогда не додумываешь требования — только фиксируешь неопределённость. ## Структура атома Frontmatter: id, type, parent, children, emits, consumes, open-questions, implementation.status, verification.status, see-also § Intent — зачем эта фича (читать первым) § Domain Rules — DR-N: бизнес-инварианты (нельзя нарушать) § Acceptance Criteria — Gherkin-сценарии (спецификация поведения) § Domain Model Touch — агрегаты и события (источник типов) § Platform: Web API — контракт endpoint/body/responses § Platform Tests — готовые тест-кейсы (адаптировать, не переписывать) § Open Questions — OQ-N: незакрытые вопросы § Decision Log — D-N: принятые решения с альтернативами ## Файловое дерево _draft/ — есть открытые OQ → НЕЛЬЗЯ реализовывать _deprecated/ — история → только читать, не трогать корень папки → верифицировано, актуально → реализовывать XXXa~param_ — ParameterChange правка (одно значение) XXXb~rule_ — RuleChange правка (новое/изменённое DR) XXXc~flow_ — FlowChange правка (шаг сценария) ~model_ — ModelChange (агрегат/поле/событие) ## Алгоритм реализации задачи 1. Найти атом: grep -r "id: ATOM-ID" specs/ 2. Проверить статус — если _draft/ или open-questions.status=open → СТОП 3. Прочитать: атом + domain.spec.md + children + see-also 4. Проверить правки: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" 5. Обновить implementation.status: none → in-progress (отдельный commit) 6. Реализовать следуя § Platform Contract. Не отступать от контракта. 7. Написать тесты. Каждый тест: // @spec ATOM-ID 8. Обновить implementation.status: in-progress → done 9. Три отдельных коммита: [ATOM-ID][spec] / [ATOM-ID][impl] / [ATOM-ID][tests] ## Создание нового атома (если нет готового) 1. Проверить что атом не существует: find specs/ -name "*slug*" 2. Определить: домен + тип + ID (следующий ×10 после последнего) 3. Создать в _draft/ с полным frontmatter 4. Все неизвестности → OQ-N с status: open 5. Если OQ есть → зафиксировать атом, СТОП, передать человеку 6. Если OQ нет → PR с переносом в корень (верификация стейкхолдером) ## При обнаружении неопределённости НЕ: принять решение самостоятельно НЕ: пропустить и продолжить ДА: добавить OQ в атом с raised-by: "agent" и raised-at: дата ДА: переместить атом в _draft/ если он был в корне ДА: зафиксировать commit с объяснением блокировки ДА: сообщить человеку что именно неопределено ## Типы изменений — когда что создавать Изменилось значение константы → ~param amendment Изменилось/добавилось DR → ~rule amendment Изменился шаг сценария → ~flow amendment Изменилась доменная модель → ~model (ModelChange, нужен человек) Изменились границы домена → .rfc (BoundaryChange, только человек) Изменился только API-контракт → обновить § Platform, не создавать правку ## Проверки перед commit □ Все DR-N из § Domain Rules реализованы? □ HTTP статусы/поля совпадают с § Platform Contract? □ Все события из emits: эмитируются в коде? □ Каждый тест имеет // @spec ATOM-ID? □ implementation.status обновлён? □ Нет pending amendments к этому атому? □ consumes-события — их источники не изменились в этом PR? ## Абсолютные запреты ✗ Реализовывать атом из _draft/ или с open OQ ✗ Удалять .spec.md файлы (только deprecated) ✗ Создавать файл в корне минуя _draft/ ✗ Изменять Domain Rules без ~rule правки ✗ Расширять scope существующего атома ✗ Принимать решения за аналитика или product ✗ Смешивать spec + impl + tests в одном commit ## Commit message формат [ATOM-ID][тип] краткое описание тип: spec | impl | tests | amend | model | fix Если правка: [ATOM-IDa][amend] ParameterChange: timeout 20s→60s Если OQ: [ATOM-ID][blocked] OQ-1: поведение при email-конфликте
Как использовать промпт Скопируйте содержимое целиком в системный промпт агента (Claude Code, Cursor, Copilot Workspace). Дополните контекстом вашего проекта: технологический стек, домены, соглашения команды. Промпт специально написан в машиночитаемом формате — без лишних слов, с явными разделами.

Что добавить под проект

К базовому промпту добавьте специфику вашего проекта одним блоком:

дополнение к промпту — специфика проекта
## Контекст проекта Стек: Node.js / TypeScript / PostgreSQL / Redis Тесты: Jest, файлы в src/__tests__/ Домены: AUTH, PAYMENT, ORDER, CATALOG, DELIVERY Команды: Team A (catalog), Team B (cart+order), Team C (delivery) Платформы: web-api (основная), mobile (iOS/Android), admin-panel Events bus: RabbitMQ, exchange: domain.events ## Текущий спринт Sprint-14. Цель: Google OAuth + SBP payments. Мои задачи: AUTH-REG-020, AUTH-LOG-020, PAY-INI-030 Блокеры: AUTH-MERGE-001 в _draft/ (OQ-1 открыт)

Atomic Spec + ИИ-агенты

ИИ-агенты — это новый тип участника команды. Они пишут код, создают тесты, предлагают рефакторинг. Но у них есть фундаментальная проблема: нет памяти между сессиями и нет контекста о решениях команды.

Atomic Spec решает это элегантно: репозиторий .spec.md файлов — это внешняя память агента. Всё что нужно знать об истории, решениях и текущем состоянии — в файлах, которые агент может прочитать в любой момент.

Ключевая идея Агент не хранит контекст в голове — он читает его из .spec.md файлов перед каждой задачей. Spec-файлы — это интерфейс между людьми и агентами.

Проблемы при работе с агентами без системы

ПроблемаЧто происходитКак часто
Агент не знает «почему»Переписывает логику которая кажется ему нелогичной, но была принята по бизнес-причинам зафиксированным в Decision LogКаждая сессия
Агент не знает текущий статусПишет код для требования которое уже deprecated или ещё в draftЧасто
Агент не видит зависимостиМеняет доменную модель не зная что это ломает другие use-casesПри рефакторинге
Агент придумывает поведениеПри неполном контексте галлюцинирует «разумное» поведение вместо следования спекуПри edge cases
Нет версионирования измененийАгент меняет спек и код вместе без явной классификации что именно изменилосьПостоянно

Как Atomic Spec решает проблемы агентов

что агент делает в начале каждой задачи
# 1. Прочитать доменную модель — понять язык предметной области cat specs/payment/domain.spec.md # 2. Найти атом задачи — прочитать Intent, DR, Acceptance Criteria cat specs/payment/initiation/PAY-INI-030_sbp-qr-payment.spec.md # 3. Проверить статус — не draft ли, нет ли pending amendments grep -r "amends: PAY-INI-030" specs/ -l grep "status:" specs/payment/initiation/PAY-INI-030.spec.md | head -5 # 4. Понять зависимости — что emits и что consumes grep -r "consumes.*PaymentInitiated\|see-also.*PAY-INI-030" specs/ -l # 5. Проверить Decision Log — почему именно так grep -A 5 "Decision Log" specs/payment/initiation/PAY-INI-030.spec.md

Роль агента в команде

Агент выступает в одной из четырёх ролей в зависимости от задачи. Роль определяет какие файлы читать и что создавать.

Роль агентаЗадачаЧитаетСоздаёт / изменяет
ImplementerРеализация фичи по спеку§ Platform, § Domain RulesКод, обновляет implementation.status
Spec WriterСоздание / дополнение атомовdomain.spec.md, соседние атомыНовые .spec.md в _draft/
Test WriterНаписание тестов§ Acceptance Criteria, § Platform TestsТест-файлы, обновляет verification.status
ReviewerРевью PR с изменением спекаВесь затронутый поддоменКомментарии, OQ в атомах при нахождении проблем
Важное ограничение Агент не верифицирует атомы — это роль стейкхолдеров-людей. Агент может создать атом в _draft/ и предложить закрыть OQ, но перенести файл в корень (верифицировать) — только человек через PR.

Рабочие процессы с агентом

Workflow 1 — Агент реализует фичу

агент реализует PAY-INI-030
# Человек даёт задачу: "Реализуй PAY-INI-030_sbp-qr-payment" # Агент делает (в порядке): 1. Читает domain.spec.md → понимает Money, Payment, ProviderSession 2. Читает PAY-INI-030.spec.md → Intent, DR-1..DR-5, Platform контракт 3. Проверяет нет ли pending amendments: grep "amends: PAY-INI-030" specs/ 4. Читает see-also атомы: PAY-REF-012 (черновик — возврат не реализован) 5. Пишет код строго по § Platform: Web API контракту 6. Пишет тесты из § Platform Tests секции 7. Обновляет implementation.status: in-progress в PAY-INI-030.spec.md 8. Создаёт PR с commit message по конвенции: [PAY-INI-030][Additive] feat: SBP QR payment # Если агент находит противоречие в спеке — НЕ додумывает: НЕПРАВИЛЬНО: "Я решил что таймаут должен быть 5 минут, это логичнее" ПРАВИЛЬНО: добавить OQ в атом: "OQ-2: DR-2 говорит 15 мин, НСПК API возвращает 10 мин. Уточнить?"

Workflow 2 — Агент пишет спек по описанию

агент создаёт новый атом
# Человек описывает фичу в свободной форме: "Нужна возможность частичного возврата по СБП. Возвращаем конкретные позиции из заказа, не всю сумму." # Агент делает: 1. Читает domain.spec.md → видит Refund агрегат, текущие поля 2. Читает PAY-REF-011_partial-refund.spec.md → как работает частичный возврат для карт 3. Читает PAY-INI-030_sbp-qr-payment.spec.md → DR-5: "СБП не поддерживает частичный capture" # Агент создаёт атом с ЯВНЫМИ открытыми вопросами: specs/payment/refund/_draft/PAY-REF-012_refund-after-sbp.spec.md open-questions: OQ-1: "PAY-INI-030 DR-5 запрещает частичный capture. СБП поддерживает частичный refund отдельным API? Требует уточнения в документации НСПК." OQ-2: "Если СБП не поддерживает — предлагать только полный возврат или блокировать UI?" # Файл остаётся в _draft/ — агент НЕ переносит его в корень # PR создаётся только с _draft/ файлом для review аналитиком

Workflow 3 — Агент обнаружил изменение требования

агент создаёт amendment
# Ситуация: агент делает задачу и обнаруживает что # реальное поведение API провайдера отличается от спека # Шаг 1: определить тип изменения "DR-2 говорит QR живёт 15 минут. НСПК API возвращает expires_in=600 (10 мин)" # → это ParameterChange # Шаг 2: проверить статус атома grep "implementation.status" specs/payment/initiation/PAY-INI-030.spec.md # → in-progress — атом в разработке # Шаг 3: создать amendment, НЕ менять основной атом touch specs/payment/initiation/PAY-INI-030a~param_sbp-qr-ttl.spec.md # заполнить: parameter.was=15, parameter.becomes=10 # conflict.status: sprint-locked (если уже в спринте) # Шаг 4: уведомить в PR description "⚠️ Обнаружено расхождение спека с реальностью провайдера. Создан PAY-INI-030a~param. Требует решения аналитика."

Правила для агента — свод

правила — нарушение любого критично
── ЧТО ВСЕГДА ДЕЛАТЬ ────────────────────────────────────────────────── ✓ ЧИТАТЬ domain.spec.md перед любой задачей в домене ✓ ЧИТАТЬ атом полностью: Intent → DR → Acceptance Criteria → Platform ✓ ПРОВЕРЯТЬ нет ли pending amendments: grep "amends: ATOM-ID" specs/ ✓ СЛЕДОВАТЬ § Platform контракту буквально — endpoint, коды ошибок, поля ✓ СОЗДАВАТЬ атомы только в _draft/ — верификация только людьми ✓ ФИКСИРОВАТЬ противоречия как OQ — не додумывать самостоятельно ✓ ОБНОВЛЯТЬ implementation.status при начале и завершении работы ✓ ПИСАТЬ commit message по конвенции: [ID][ChangeType] описание ✓ СОЗДАВАТЬ amendment при обнаружении расхождения спека с реальностью ✓ ПРОВЕРЯТЬ зависимости через emits/consumes перед изменением домена ── ЧТО НИКОГДА НЕ ДЕЛАТЬ ────────────────────────────────────────────── ✗ НЕ УДАЛЯТЬ атомы — только deprecated ✗ НЕ ПЕРЕНОСИТЬ файлы из _draft/ в корень самостоятельно ✗ НЕ ИЗМЕНЯТЬ атомы в _deprecated/ ✗ НЕ ДОДУМЫВАТЬ поведение если в спеке нет ответа — создать OQ ✗ НЕ РАСШИРЯТЬ scope атома — создавать новый рядом ✗ НЕ МЕНЯТЬ существующие DR напрямую — только через amendment ✗ НЕ НАЧИНАТЬ реализацию атома из _draft/ без явного разрешения ✗ НЕ ИГНОРИРОВАТЬ pending amendments к реализуемому атому

Системный промпт агента

Готовый промпт для вставки в system prompt или .cursorrules / AGENTS.md. Содержит все нюансы методологии в компактной форме.

AGENTS.md / .cursorrules / system prompt
# Atomic Spec — инструкция для ИИ-агента ## Роль и контекст Ты работаешь в репозитории использующем методологию Atomic Spec. Все требования хранятся в `specs/` в виде `.spec.md` файлов. Файлы — единственный источник правды о системе и её истории. ## Перед каждой задачей — обязательно прочитать 1. `specs/[домен]/domain.spec.md` — доменная модель, агрегаты, события, Decision Log 2. Целевой атом полностью: `§ Intent` → `§ Domain Rules` → `§ Acceptance Criteria` → `§ Platform` 3. Проверить pending amendments: `grep -r "amends: [ATOM-ID]" specs/ -l` 4. Проверить зависимости: поля `see-also`, `emits`, `consumes` в frontmatter ## Структура файлов ``` specs/[domain]/ domain.spec.md — модель домена (читать первым) _index.md — дашборд директории _draft/ — незакрытые OQ, нельзя реализовывать _deprecated/ — история, никогда не изменять DOMAIN-TYPE-NNN_slug.spec.md — верифицированный активный атом DOMAIN-TYPE-NNNa~param_slug.spec.md — amendment (правка) ``` ## Именование - Атом: `DOMAIN-TYPE-NNN_action-noun.spec.md` (AUTH-REG-010_phone-otp-registration) - Amendment: `BASE-IDx~type_slug.spec.md` где type: param | rule | flow | model - Создаёшь новый — шаг нумерации 10 (010, 020, 030), резерв для вариантов ## Frontmatter — ключевые поля ```yaml id, type, title, parent, children # структура emits, consumes, see-also # зависимости open-questions: # причина быть в _draft/ - id: OQ-N, question, status: open|resolved implementation.status: none|in-progress|done verification.status: none|in-progress|passed|failed verification.blocks-release: true|false ``` ## Типы изменений — обязательно классифицировать | Тип | Когда | Что делать | |-----|-------|-----------| | ParameterChange | значение константы | amendment ~param | | RuleChange | добавить/изменить DR | amendment ~rule | | FlowChange | изменить сценарий | amendment ~flow | | ModelChange | агрегат/поле/событие | ~model файл на уровне домена, блокирует спринт | | BoundaryChange | перенос между доменами | .rfc.md, требует RFC и голосования | | PlatformChange | только API-контракт | правка § Platform секции | ## Правила работы со спеком ### Реализация фичи 1. Прочитать domain.spec.md домена 2. Прочитать целевой атом полностью 3. Проверить amendments: `grep "amends: [ID]" specs/ -l` 4. Следовать `§ Platform` контракту буквально — endpoint, поля, коды ошибок 5. Тесты писать из `§ Acceptance Criteria` и `§ Platform Tests` 6. Обновить `implementation.status: in-progress` → `done` 7. Commit: `[ATOM-ID][ChangeType] краткое описание` + Requested-by, Reason, Affects ### Создание нового атома 1. Создать ТОЛЬКО в `_draft/` — верификация только людьми 2. Все неизвестности → `open-questions` с `status: open` 3. Не переносить из `_draft/` в корень самостоятельно 4. PR description: список OQ требующих ответа от аналитика ### Обнаружил расхождение спека с реальностью 1. Определить тип изменения (см. таблицу выше) 2. Проверить статус атома (in-progress или done?) 3. Создать amendment-файл рядом: `BASE-IDa~type_slug.spec.md` 4. Заполнить `conflict.status`: `sprint-locked` | `pulled` | `merged` 5. НЕ менять основной атом напрямую 6. Указать в PR description: "⚠️ Amendment создан, требует решения аналитика" ### Изменение доменной модели (ModelChange) 1. Создать `DOMAIN~MODEL-NNN~model_slug.spec.md` в корне домена 2. Заполнить `affects-atoms` — все атомы затронутые изменением 3. Добавить `§ Model Delta` с diff агрегатов 4. Заполнить `§ Decision Log` — решение обязательно 5. Не начинать реализацию пока model-change не смержен в main ## Запрещено категорически - Удалять `.spec.md` файлы (только deprecated) - Переносить файлы из `_draft/` в корень - Изменять файлы в `_deprecated/` - Додумывать поведение без фиксации OQ - Расширять scope существующего атома - Менять DR напрямую в основном атоме (только через amendment) - Начинать реализацию атомов из `_draft/` - Игнорировать pending amendments к реализуемому атому - Верифицировать атомы (approve PR) — это роль людей ## Commit convention ``` [ATOM-ID][ChangeType] action: description Requested-by: Name / Role Reason: краткая причина Breaking: true|false Affects: ATOM-ID-1, ATOM-ID-2 ``` ChangeType: Additive | Breaking | ParameterChange | RuleChange | ModelChange | PlatformChange ## Когда создавать OQ вместо решения - Спек противоречит реальному поведению API/системы - Два DR конфликтуют друг с другом - Edge case не покрыт в Acceptance Criteria - Поведение зависит от бизнес-решения не технического - Неясно должна ли фича быть отдельным атомом или частью существующего ## Ответ на вопрос "что мне делать" Если задача не привязана к конкретному атому: 1. Найти подходящий домен: `ls specs/` 2. Найти ближайший атом: `grep -r "title" specs/[domain]/ --include="*.spec.md"` 3. Если атома нет — создать в `_draft/` и сообщить что нужна верификация 4. Если атом в `_draft/` — сообщить что нужно закрыть OQ перед реализацией

Примеры диалогов с агентом

✅ Правильно: агент нашёл проблему и создал OQ

Диалог Человек: «Реализуй частичный возврат для СБП»

Агент: «Прочитал PAY-INI-030. DR-5 явно говорит: "СБП не поддерживает частичный capture — только полная сумма". Частичный возврат требует отдельного API НСПК — я не знаю поддерживает ли он это.

Создал PAY-REF-012_refund-after-sbp.spec.md в _draft/ с двумя открытыми вопросами:
OQ-1: Поддерживает ли НСПК API частичный возврат отдельным методом?
OQ-2: Если нет — предлагать только полный возврат или блокировать UI?

Реализацию начну после того как аналитик закроет OQ-1 и OQ-2.»

✅ Правильно: агент обновил статус и создал amendment

Диалог Человек: «Реализуй PAY-INI-030, НСПК API уже поднят в staging»

Агент: «Реализую. В процессе обнаружил расхождение: DR-2 говорит TTL=15 мин, НСПК возвращает expires_in=600 (10 мин). Код написан под 10 мин как реальное поведение.

Создал amendment PAY-INI-030a~param_sbp-qr-ttl.spec.md: was=15, becomes=10, conflict.status=sprint-locked.

PR готов. В description отметил: ⚠️ Amendment требует решения аналитика — либо обновить DR-2, либо подтвердить что 15 мин были опечаткой.»

❌ Неправильно: агент додумал поведение

Антипаттерн Человек: «Реализуй частичный возврат для СБП»

Агент (НЕПРАВИЛЬНО): «Реализовал! Я решил что раз СБП это банковский перевод, то возврат работает как обычный перевод в обратную сторону. Сделал POST /v1/refunds/sbp с полем items[]. Если провайдер не поддерживает — вернём ошибку PROVIDER_NOT_SUPPORTED.»

Проблема: Агент создал API-контракт из воздуха, игнорировал DR-5, не создал OQ. Теперь в коде есть endpoint которого нет в спеке и который может противоречить реальному НСПК API.

Антипаттерны при работе агента со спеком

АнтипаттернКак выглядитПравильно
Тихое расширениеАгент добавляет поля в API response которых нет в спеке «для удобства»Создать OQ или PlatformChange amendment
СамоверификацияАгент создаёт атом и сам же переносит его из _draft/ в кореньТолько PR + апрув человека = верификация
Молчаливый deprecatedАгент видит что атом устарел и удаляет его «чтобы не мусорить»Переместить в _deprecated/ с причиной
Scope creepАгент добавляет функциональность в атом потому что «логично тут»Новая функциональность = новый атом
DR overrideАгент меняет бизнес-правило в атоме потому что «так лучше технически»Создать RuleChange amendment с обоснованием
Draft bypassАгент сразу пишет код для фичи которая ещё в _draft/Сначала закрыть OQ → верификация → потом код

Atomic Spec как операционная система для AI-агентов

Atomic Spec — не просто документация. Для AI-агентов это машиночитаемый контракт: что делать, что нельзя делать, что неизвестно. Агенты не интерпретируют требования — они исполняют их. Это радикально снижает риск галлюцинаций и отклонений от намерения команды.

Ключевое свойство: атом явно разделяет знание и незнание. Если информации нет в атоме — агент не предполагает, он создаёт Open Question и останавливается. Это единственный безопасный способ работы агентов с требованиями.

Без Atomic SpecС Atomic Spec
Агент получает текст задачи в свободной форме → интерпретирует → делает что-то похожее на нужноеАгент читает атом → следует § Domain Rules → реализует § Platform Contract → запускает § Platform Tests
Непонятное требование → агент додумываетНепонятное требование → агент создаёт OQ → стоп
Изменение требования → агент не знаетИзменение требования → amendment файл → агент видит при чтении
Граница между доменами → агент угадываетemits/consumes в frontmatter → граница явная

Три агента — три специализации

Архитектура мультиагентной системы на основе Atomic Spec строится из трёх специализированных агентов, координируемых оркестратором:

Агент-аналитик

Работает со спецификацией. Создаёт и обновляет атомы. Закрывает Open Questions. Классифицирует изменения.

Входные данные:
Запрос на фичу / текст изменения / решение стейкхолдера
Выходные данные:
Готовый .spec.md в корне или OQ-список для человека
Агент-разработчик

Читает готовые атомы. Реализует платформенный контракт. Обновляет implementation.status. Пишет тесты с @spec.

Входные данные:
Атом в корне папки (не draft, нет открытых OQ)
Выходные данные:
Код + тесты + обновлённый frontmatter
Агент-тестировщик

Читает diff между релизами. Запускает тесты. Сверяет поведение с § Acceptance Criteria. Обновляет verification.status.

Входные данные:
git diff release/v1..release/v2 + реализованный атом
Выходные данные:
Отчёт + обновлённый verification.status

Агент-оркестратор — главный дирижёр

Оркестратор не пишет код и не создаёт атомы напрямую. Его работа — управлять потоком: определить какой агент нужен, передать правильный контекст, получить результат, проверить что можно двигаться дальше.

алгоритм оркестратора — полный цикл задачи
ПОЛУЧЕНА ЗАДАЧА: "Добавить регистрацию через GitHub OAuth" ─── ШАГ 1: РАЗВЕДКА СОСТОЯНИЯ ────────────────────────────── find specs/ -name "*github*" ! -path "*/_deprecated/*" → Файлов нет. Атом не существует. cat specs/auth/domain.spec.md | grep -A5 "provider:" → provider: "phone" | "google" ← GitHub ещё не в домене Вывод оркестратора: Нужен ModelChange (domain.spec.md) + новые атомы Порядок: сначала ModelChange, потом создать атомы, потом реализация ─── ШАГ 2: ДЕЛЕГИРОВАТЬ АНАЛИТИКУ ───────────────────────── → ВЫЗОВ агент-аналитик: задача: "обновить domain.spec.md добавив github в provider enum" тип_изменения: Additive ← не Breaking, только расширение enum создать_атомы: [AUTH-REG-030, AUTH-REG-031, AUTH-LOG-030] ← РЕЗУЛЬТАТ аналитика: domain.spec.md обновлён (v3.1) AUTH-REG-030 создан в _draft/ с OQ-1 открытым OQ-1: "GitHub может не вернуть email — как идентифицировать юзера?" ─── ШАГ 3: ОРКЕСТРАТОР ОЦЕНИВАЕТ ────────────────────────── Есть открытые OQ → нельзя делегировать разработчику → СТОП. Передать OQ-1 человеку (PM/аналитику). → Зафиксировать состояние в git, уведомить команду. ─── ШАГ 4: ПОСЛЕ ЗАКРЫТИЯ OQ ─────────────────────────────── Человек закрыл OQ-1: "использовать githubId как identifier, email опционален" Аналитик обновил атом, перенёс в корень папки → ВЫЗОВ агент-разработчик: задача: AUTH-REG-030, AUTH-REG-031, AUTH-LOG-030 контекст: domain.spec.md + атомы + see-also ← РЕЗУЛЬТАТ разработчика: implementation.status: done для всех трёх PR #441 открыт ─── ШАГ 5: ДЕЛЕГИРОВАТЬ ТЕСТИРОВЩИКУ ────────────────────── → ВЫЗОВ агент-тестировщик: задача: верифицировать AUTH-REG-030, AUTH-REG-031, AUTH-LOG-030 релиз_дельта: git diff release/Sprint-13..HEAD -- specs/ ← РЕЗУЛЬТАТ тестировщика: AUTH-REG-030: verification.status: passed AUTH-REG-031: verification.status: failed — edge case с пустым email ─── ШАГ 6: ОРКЕСТРАТОР РЕШАЕТ ────────────────────────────── AUTH-REG-031 failed → создать amendment или баг? → Это нарушение DR, не новое требование → баг, вернуть разработчику → Делегировать агент-разработчик: fix AUTH-REG-031
Главная функция оркестратора Оркестратор знает в каком состоянии находится каждый атом и какой агент может с ним работать. Он никогда не передаёт задачу агенту если атом в _draft/ или есть открытые OQ. Это единственная гарантия качества в мультиагентной системе.

Агент-аналитик — протокол работы

агент-аналитик: полный протокол
## ПОЛУЧЕНИЕ ЗАДАЧИ Входит: текстовое описание требования / решения / изменения ## ШАГ 1: КЛАССИФИКАЦИЯ Определить тип изменения перед любым действием: Новая фича без затрагивания домена → новый use-case + scenarios Изменение значения параметра → ~param amendment Изменение бизнес-правила → ~rule amendment Изменение агрегата / события → ~model (требует человека) Изменение границ домена → .rfc (только человек, СТОП) ## ШАГ 2: ПРОВЕРКА ДОМЕНА cat specs/{domain}/domain.spec.md → Модель поддерживает фичу? Если нет → сначала ModelChange → Нет ли похожего существующего атома? find specs/ -name "*.spec.md" | xargs grep -l "{ключевые слова}" -i ## ШАГ 3: СОЗДАНИЕ АТОМА Генерация ID: домен + тип + следующий ×10 ls specs/{domain}/{section}/ | grep "^DOMAIN-TYPE" | sort -t- -k3 -n | tail -1 Создать файл: specs/{domain}/{section}/_draft/DOMAIN-TYPE-NNN_slug.spec.md ВСЕГДА в _draft/ — никогда в корне ## ШАГ 4: ЗАПОЛНЕНИЕ СЕКЦИЙ В ПОРЯДКЕ 1. frontmatter ← id, type, parent, children (пустые), emits, consumes 2. § Intent ← одно предложение: кто, что делает, какой результат 3. § Domain Rules ← DR-N нумеруются от 1. Каждый — одно атомарное правило 4. § Acceptance Criteria ← Gherkin. Ссылки [→ ID] на leaf-атомы 5. § Domain Model Touch ← только агрегаты которые создаются/меняются 6. § Platform: Web API ← контракт. Если неизвестен → OQ 7. § Open Questions ← всё неизвестное. Лучше больше OQ чем додумать ## ШАГ 5: РЕШЕНИЕ О ПЕРЕНОСЕ В КОРЕНЬ grep "status: open" specs/{path}/_draft/{atom}.spec.md Если есть open OQ → СТОП. Зафиксировать. Передать оркестратору список OQ. Если OQ нет → коммит в _draft/, сигнал оркестратору для верификации Перемещение в корень делает ЧЕЛОВЕК через PR — не агент ## ПРАВКИ К СУЩЕСТВУЮЩИМ АТОМАМ Найти атом: grep -r "id: TARGET-ID" specs/ Определить тип правки (~param / ~rule / ~flow) Создать amendment файл: TARGET-IDx~type_slug.spec.md Заполнить: amends, was, becomes, conflict (если sprint-locked) НЕ изменять исходный атом — только amendment рядом

Агент-разработчик — протокол работы

агент-разработчик: полный протокол
## ПОЛУЧЕНИЕ ЗАДАЧИ Входит: ID атома (или список ID) ## ШАГ 1: ПРЕДСТАРТОВЫЕ ПРОВЕРКИ ① Атом существует и не в _draft/? find specs/ -name "{ATOM-ID}*" ! -path "*/_draft/*" ! -path "*/_deprecated/*" Нет → СТОП. Сообщить оркестратору: атом не готов. ② Нет открытых OQ? grep "status: open" specs/{path}/{atom}.spec.md Есть → СТОП. Сообщить оркестратору: OQ не закрыты. ③ Нет незакрытых правок? find specs/ -name "{ATOM-ID}*~*" ! -path "*/_deprecated/*" \ | xargs grep -l "amendment-status: pending" 2>/dev/null Есть → прочитать правку перед реализацией (приоритет над оригиналом) ## ШАГ 2: ЗАГРУЗКА КОНТЕКСТА (только нужное) ЧИТАТЬ: целевой атом + domain.spec.md + все children + все see-also НЕ ЧИТАТЬ: deprecated атомы, несвязанные домены, всю папку specs/ ## ШАГ 3: ОБНОВИТЬ СТАТУС implementation.status: none → in-progress git commit -m "[{ATOM-ID}][spec] mark in-progress" ## ШАГ 4: РЕАЛИЗАЦИЯ — СТРОГО ПО КОНТРАКТУ Источник типов: § Domain Model Touch → агрегаты и поля Источник логики: § Domain Rules DR-N → каждый реализовать явно Источник сигнатур: § Platform: Web API → endpoint, body, responses Источник тестов: § Platform Tests → адаптировать, не изобретать Если контракт неоднозначен → НЕ додумывать. Создать OQ. СТОП. Если DR противоречат друг другу → НЕ выбирать. Создать OQ. СТОП. ## ШАГ 5: ТРАССИРОВКА ТЕСТОВ Каждый тест обязан иметь @spec комментарий: // @spec AUTH-REG-030 DR-1 — githubId уникален per provider it('создаёт Identity с provider: github', ...) Структура тестов: один тест → один Scenario из § Acceptance Criteria Покрытие: все DR-N должны иметь хотя бы один тест ## ШАГ 6: ПРОВЕРКА ПЕРЕД КОММИТОМ Все DR-N реализованы и есть тест для каждого? HTTP коды / поля / события совпадают с § Platform Contract? Все emits эмитируются в коде? Каждый тест имеет @spec? Нет кода зависящего от deprecated атомов? ## ШАГ 7: ФИНАЛЬНЫЙ КОММИТ (три отдельных) git commit -m "[{ATOM-ID}][impl] {краткое описание}" git commit -m "[{ATOM-ID}][tests] acceptance criteria coverage" Затем обновить: implementation.status: in-progress → done git commit -m "[{ATOM-ID}][spec] mark done, PR #{номер}"

Агент-тестировщик — протокол работы

агент-тестировщик: полный протокол
## ПОЛУЧЕНИЕ ЗАДАЧИ Входит: список атомов для верификации + тег релиза ## ШАГ 1: ПОСТРОИТЬ СПИСОК ЧТО ТЕСТИРОВАТЬ ① Новые и изменённые атомы в релизе: git diff release/Sprint-13..release/Sprint-14 -- specs/ --name-status ② Определить приоритет: grep -l "blocks-release: true" {список файлов} → Сначала blocks-release: true. Потом остальные. ③ Проверить правки — они меняют сценарии: find specs/ -name "*~*" ! -path "*/_deprecated/*" \ | xargs grep -l "amendment-status: pending" 2>/dev/null ## ШАГ 2: ДЛЯ КАЖДОГО АТОМА — ДЕЛЬТА СЦЕНАРИЕВ git diff release/Sprint-13..release/Sprint-14 \ -- specs/{path}/{atom}.spec.md | grep "^[+-]" | grep -v "^---\|^+++" → Строки с + : новые/изменённые сценарии → нужно проверить → Строки с - : удалённые сценарии → убедиться что поведение убрано ## ШАГ 3: ВЕРИФИКАЦИЯ СООТВЕТСТВИЯ Для каждого Scenario из § Acceptance Criteria: Given/When/Then → выполнить сценарий (автотест или API-вызов) Результат → сравнить с Then/And блоками дословно DR-N → каждое правило проверить отдельно ВАЖНО: тестировщик проверяет соответствие атому — не своё понимание Если поведение «кажется правильным» но противоречит атому → FAILED Если поведение «кажется странным» но соответствует атому → PASSED ## ШАГ 4: ОБНОВИТЬ СТАТУС Все сценарии прошли: verification.status: none → passed verified-by: "agent/автотест" git commit -m "[{ATOM-ID}][verification] passed — Sprint-14" Хотя бы один сценарий не прошёл: verification.status: none → failed failed-scenarios: [список] failed-reason: "описание расхождения с § Acceptance Criteria" git commit -m "[{ATOM-ID}][verification] FAILED — {сценарий}" ## ШАГ 5: ОТЧЁТ ОРКЕСТРАТОРУ passed: [AUTH-REG-030, AUTH-LOG-030] failed: [AUTH-REG-031] AUTH-REG-031: Scenario "GitHub email отсутствует" Expected: Identity создаётся без email (DR-1) Actual: 500 NullPointerException при email=null blocking-release: true ← AUTH-REG-031 blocks-release: true

Полный цикл: от задачи до релиза

полный цикл мультиагентной работы
ОРКЕСТРАТОР АНАЛИТИК РАЗРАБОТЧИК ТЕСТИРОВЩИК ───────────────────────────────────────────────────────────────────── Получить задачу │ ├─ Разведка: есть атом? │ Нет → вызов АНАЛИТИКА ──→ Создать в _draft/ │ Найти OQ ──────────→ СТОП → человеку │ Нет OQ ────────────→ сигнал орк. │ ├─ Атом готов (в корне, нет OQ) │ → вызов РАЗРАБОТЧИКА ───────────────────────→ Прочитать атом │ Проверить правки │ Реализовать │ Написать тесты │ Обновить статус │ Сигнал орк. │ ├─ Implementation: done │ → вызов ТЕСТИРОВЩИКА ──────────────────────────────────────────→ │ Дельта сценариев │ Верифицировать │ Обновить статус │ Отчёт орк. │ ├─ Verification: passed → ГОТОВО К РЕЛИЗУ ├─ Verification: failed → вернуть РАЗРАБОТЧИКУ (баг) │ или вернуть АНАЛИТИКУ (атом неверен) │ └─ Все blocks-release: passed → СИГНАЛ: РЕЛИЗ РАЗБЛОКИРОВАН

Стопы и сигналы — явные состояния системы

Каждый агент знает три состояния: работаю / стоп-блокер / стоп-эскалация.

СитуацияАгентДействиеКому сигнал
Атом в _draft/РазработчикСТОПОркестратор → аналитик
Открытый OQ найденАналитикСТОП + зафиксировать OQОркестратор → человек
Неоднозначный контрактРазработчикСТОП + OQ в атомОркестратор → аналитик
ModelChange обнаруженАналитикСТОП + создать ~model файлОркестратор → человек (архитектор)
BoundaryChangeЛюбойСТОП немедленноОркестратор → CTO + архитектор
Verification: failedТестировщикСТОП релиза + отчётОркестратор → разработчик
DR противоречат друг другуРазработчикСТОП + OQ в оба атомаОркестратор → аналитик
Amendment sprint-lockedОркестраторРешить: изъять или следующий PRЧеловек (PM)

Протокол передачи задачи между агентами

Каждая передача задачи — структурированное сообщение. Агент не получает «задачу текстом», он получает пакет контекста:

формат пакета передачи задачи
# Оркестратор → Разработчик AGENT_TASK: target_agent: developer primary_atoms: [AUTH-REG-030, AUTH-REG-031] context_atoms: [domain.spec.md, AUTH-MERGE-001] amendments: [] ← правок нет tech_stack: Node.js / TypeScript / Jest / PostgreSQL platform: web-api constraints: - все тесты должны проходить npm test перед коммитом - не менять domain.spec.md без сигнала оркестратору done_when: - implementation.status: done в каждом атоме - все @spec тесты зелёные - нет pending amendments # Агент → Оркестратор (результат) AGENT_RESULT: agent: developer status: done # done | blocked | escalate completed: [AUTH-REG-030, AUTH-REG-031] pr: github.com/org/repo/pull/441 notes: "AUTH-REG-031: edge case с null email обработан через Optional" # Агент → Оркестратор (блокер) AGENT_RESULT: agent: developer status: blocked atom: AUTH-REG-031 reason: OQ-2 открыт: поведение при rate limit от GitHub API не описано oq_added: true needs: product decision on GitHub API rate limit handling

SKILL.md — инструкция для агента-оркестратора

Скопируйте содержимое в файл SKILL.md в корне репозитория или передайте как системный промпт агенту-оркестратору. Файл спроектирован как самодостаточная инструкция — агент прочитает его один раз и будет работать по методологии.

SKILL.md — Atomic Spec Orchestrator
# SKILL: Atomic Spec Orchestrator # Версия: 1.0 | Читать целиком перед началом работы ## ЧТО ТАКОЕ ATOMIC SPEC Каждое требование — отдельный .spec.md файл (атом). Атомы хранятся в specs/ рядом с кодом, версионируются через git. Позиция файла в дереве = его статус. Никаких внешних систем. _draft/ → есть открытые вопросы, нельзя реализовывать _deprecated/ → история, не трогать, не удалять корень/ → верифицировано, активно, можно реализовывать ## СТРУКТУРА АТОМА frontmatter (YAML): id, type, parent, children, emits, consumes open-questions: [{id, question, status: open|resolved}] implementation: {status: none|in-progress|done, sprint, pr} verification: {status: none|in-progress|passed|failed, blocks-release} секции (Markdown): § Intent — бизнес-намерение (1 абзац) § Domain Rules — DR-N: инварианты (нарушать запрещено) § Acceptance Criteria — Gherkin-сценарии § Domain Model Touch — агрегаты и события § Platform: Web API — endpoint/body/responses контракт § Platform Tests — готовые тест-кейсы § Open Questions — OQ-N незакрытые вопросы § Decision Log — D-N принятые решения ## ТВОЯ РОЛЬ: ОРКЕСТРАТОР Ты управляешь тремя специализированными подагентами: analyst — создаёт/обновляет атомы, закрывает OQ developer — реализует атомы из корня (не draft) tester — верифицирует реализацию по сценариям Ты не пишешь код. Не создаёшь атомы напрямую. Ты читаешь состояние дерева, решаешь кого вызвать, эскалируешь блокеры. ## АЛГОРИТМ ОРКЕСТРАТОРА — НОВАЯ ЗАДАЧА 1. РАЗВЕДКА find specs/ -name "*{ключевые слова}*" ! -path "*/_deprecated/*" Атом существует? Где? В _draft/ или корне? Есть ли open-questions? Есть ли правки (~param, ~rule, ~flow, ~model)? 2. МАРШРУТИЗАЦИЯ Атома нет → вызвать analyst Атом в _draft/ + open OQ → вызвать analyst, затем эскалировать OQ человеку Атом в _draft/ + все OQ=resolved → сигнал: нужна верификация человека (PR) Атом в корне + impl=none → вызвать developer Атом в корне + impl=done → вызвать tester Verification: failed → вернуть developer с отчётом Verification: passed → задача завершена 3. ПЕРЕДАЧА ЗАДАЧИ (всегда структурированный пакет) target_agent, primary_atoms, context_atoms, tech_stack, platform, amendments, constraints, done_when 4. ПОЛУЧЕНИЕ РЕЗУЛЬТАТА status: done → перейти к следующему шагу status: blocked → прочитать reason, эскалировать OQ человеку status: escalate → ModelChange/BoundaryChange → только человек ## ПРОТОКОЛ АГЕНТ-АНАЛИТИК Входит: текст требования / решения / изменения 1. Классифицировать тип изменения: Новая фича без изменения домена → новый use-case + scenarios в _draft/ Изменение значения параметра → ~param amendment Изменение бизнес-правила → ~rule amendment Изменение агрегата/события → ~model файл → СТОП → человеку Изменение границ домена → .rfc файл → СТОП → CTO + архитектор 2. Прочитать domain.spec.md — домен поддерживает фичу? 3. Проверить нет ли похожего атома: grep -r "{keywords}" specs/ 4. Создать атом ТОЛЬКО в _draft/ 5. Заполнить: Intent → DR-N → Acceptance Criteria → Platform Contract 6. Всё неизвестное → OQ-N с status: open 7. Если OQ открыты → СТОП, вернуть список OQ оркестратору Если OQ нет → коммит, сигнал оркестратору: нужна верификация Выходит: путь к атому + список открытых OQ (пустой если готово) ## ПРОТОКОЛ АГЕНТ-РАЗРАБОТЧИК Входит: список ID атомов + tech_stack + platform 1. Проверить: атом не в _draft/ и нет open OQ → иначе СТОП 2. Проверить правки: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" Правки имеют приоритет над оригинальным атомом 3. Загрузить: атом + domain.spec.md + children + see-also (только это) 4. Обновить implementation.status: none → in-progress (отдельный commit) 5. Реализовать строго по: § Domain Rules — каждый DR-N реализован явно § Platform Contract — endpoint/body/responses точно по спеку § Domain Model Touch — типы и агрегаты из атома 6. Тесты: каждый тест → @spec ATOM-ID DR-N 7. Неоднозначность в контракте → создать OQ → СТОП → оркестратору 8. Три раздельных коммита: [ATOM-ID][impl] / [tests] / [spec] mark done Выходит: implementation.status=done + PR + список любых новых OQ ## ПРОТОКОЛ АГЕНТ-ТЕСТИРОВЩИК Входит: список атомов + тег релиза для сравнения 1. Построить список по приоритету: сначала: blocks-release: true + verification.status: none потом: blocks-release: false 2. Для каждого атома получить дельту сценариев: git diff {prev-tag}..HEAD -- specs/{path}/{atom}.spec.md 3. Верифицировать каждый Scenario из § Acceptance Criteria Соответствие атому — источник истины, не своё суждение 4. Проверить все DR-N явно (не только happy path) 5. Если passed: обновить verification.status: passed + commit Если failed: verification.status: failed + описать расхождение с атомом 6. Отчёт: passed[], failed[], blocking-release: bool Выходит: обновлённые verification.status + отчёт оркестратору ## АБСОЛЮТНЫЕ ЗАПРЕТЫ (для всех агентов) Реализовывать атом из _draft/ или с open OQ Удалять .spec.md файлы (только deprecated) Создавать атом в корне минуя _draft/ Изменять Domain Rules без ~rule правки Расширять scope атома — создавать новый Принимать архитектурные решения (ModelChange, BoundaryChange) Смешивать spec + impl + tests в одном commit Додумывать поведение при неопределённости ## СТОПЫ И ЭСКАЛАЦИИ Открытый OQ → СТОП → человеку (PM/аналитик) ModelChange → СТОП → архитектор + tech lead BoundaryChange → СТОП → CTO + архитектор (RFC обязателен) Verification fail → СТОП релиза → разработчику с отчётом DR-конфликт → СТОП → OQ в оба атома → аналитику sprint-locked amend → человеку: изъять или следующий PR? ## ТИПЫ ПРАВОК И КОГДА СОЗДАВАТЬ ~param — изменилось значение константы (таймаут, лимит, размер) ~rule — изменилось/добавилось DR (бизнес-правило) ~flow — изменился шаг сценария или ветка ~model — изменился агрегат/поле/событие (эскалировать человеку) .rfc — изменились границы домена (только человек) ## ВЕРСИОНИРОВАНИЕ Domain: git tag domain/{NAME}/vX.Y vX.0 — ModelChange / BoundaryChange (Breaking) vX.Y — RuleChange / Additive нет PATCH — нет "исправлений" без смыслового изменения Platform API: честный semver vMAJOR.MINOR.PATCH Атом: ревизия r1,r2,r3 — линейная история через git log ## COMMIT MESSAGE ФОРМАТ [ATOM-ID][тип] краткое описание типы: spec | impl | tests | amend | model | verification | blocked Requested-by: имя / роль Breaking: true|false Affects: ATOM-ID, ATOM-ID (если затрагивает другие атомы) ## ПОЛЕЗНЫЕ КОМАНДЫ Что готово к реализации: grep -rl "implementation.status: none" specs/ ! -path "*/_draft/*" Что блокирует релиз: grep -rl "blocks-release: true" specs/ | xargs grep -l "verification.status: none" Незакрытые OQ: grep -r "status: open" specs/ --include="*.spec.md" -l Правки к атому: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" Кто потребляет событие: grep -r "consumes.*EventName" specs/ -l Дельта сценариев релиза: git diff release/v1..release/v2 -- specs/ --name-status
Как применять Сохраните как SKILL.md в корне репозитория. В промпте агента: «Прочитай SKILL.md и следуй этой методологии для всех задач со specs/». Для Claude Code: добавьте в CLAUDE.md. Для Cursor: в .cursorrules. Для Copilot: в системный промпт workspace.
🤖 AI-агенты и Atomic Spec

Агент как
эксперт-оркестратор
команды

Atomic Spec даёт агенту структуру для управления тремя подагентами — аналитиком, разработчиком и тестировщиком — без потери контекста и без самодеятельности.

Как работает оркестратор → SKILL.md для агента →

Модель агента-оркестратора

Агент-оркестратор — это главный агент который получает задачу от человека и управляет тремя специализированными подагентами. Каждый подагент работает строго в своей зоне ответственности. Оркестратор никогда не делает работу подагентов сам.

архитектура оркестратора
Человек (PM / Dev / Tester) │ ▼ ОРКЕСТРАТОР ← получает задачу, управляет процессом, эскалирует к человеку │ не принимает бизнес-решений самостоятельно │ не пишет код и не пишет тесты напрямую │ ├──▶ АНАЛИТИК-АГЕНТ читает/создаёт атомы, выявляет OQ, формализует DR │ │ НЕ пишет код, НЕ запускает тесты │ ▼ │ spec готов + все OQ закрыты │ ├──▶ РАЗРАБОТЧИК-АГЕНТ реализует по § Platform Contract, обновляет статус │ │ НЕ меняет атомы (только implementation.status) │ ▼ │ implementation.status: done │ └──▶ ТЕСТИРОВЩИК-АГЕНТ запускает тесты, проверяет Gherkin, обновляет verification │ НЕ меняет бизнес-логику, только верифицирует ▼ verification.status: passed | failed
Главное правило оркестратора Любое решение которое не описано в атоме — эскалация к человеку, а не самостоятельное решение. Оркестратор управляет потоком работы, но не владеет бизнес-знанием.

Три подагента — зоны ответственности

ПодагентЧитаетПишетНе делает никогда
Аналитик domain.spec.md, существующие атомы, git log Новые .spec.md в _draft/, OQ, Decision Log Код, тесты, изменение implementation.status
Разработчик Атом + domain + children + см. see-also Код, implementation.status в frontmatter Создание/изменение атомов, принятие бизнес-решений
Тестировщик § Acceptance Criteria, § Platform Tests, git diff Тест-файлы, verification.status в frontmatter Изменение требований, код приложения

Полный рабочий цикл — от задачи до PR

полный цикл оркестратора
━━━ ШАГ 0: ОРКЕСТРАТОР получает задачу ━━━━━━━━━━━━━━━━━━━ Задача: "Реализовать регистрацию через GitHub OAuth" Оркестратор выполняет: find specs/ -name "*github*" ! -path "*/_deprecated/*" grep -r "github" specs/ --include="*.spec.md" -l ━━━ ШАГ 1: → АНАЛИТИК-АГЕНТ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Вход: имя фичи + domain.spec.md + все существующие auth-атомы Задача аналитика: 1. Прочитать domain.spec.md — понять текущую модель 2. Найти аналог (AUTH-REG-020 Google OAuth) — взять как шаблон 3. Определить специфику GitHub: email может отсутствовать 4. Создать AUTH-REG-030_github-oauth-registration.spec.md в _draft/ 5. Создать дочерние сценарии: _031_no-email, _032_provider-error 6. Зафиксировать все неясности как OQ Выход аналитика (в формате JSON для оркестратора): { "atoms_created": ["AUTH-REG-030", "AUTH-REG-031", "AUTH-REG-032"], "open_questions": [ {"id": "OQ-1", "question": "Если GitHub не вернул email — создать аккаунт без email?", "blocking": true} ], "ready_to_implement": false, "blocked_by": "OQ-1" } ━━━ ШАГ 2: ОРКЕСТРАТОР — OQ есть → ЭСКАЛАЦИЯ ━━━━━━━━━━━━ Если open_questions[].blocking = true: → СТОП. Оркестратор НЕ принимает решение сам. → Формирует вопрос для человека чётко и структурно: "AUTH-REG-030 заблокирован. OQ-1: GitHub OAuth может не вернуть email. Три варианта: A) Создать аккаунт без email (риск: нет email для уведомлений) B) Отклонить регистрацию без email (риск: часть пользователей не войдёт) C) Запросить email отдельно (риск: дополнительный шаг, friction) Какой вариант выбираем?" Человек отвечает: "Вариант A" ━━━ ШАГ 3: → АНАЛИТИК-АГЕНТ (продолжение) ━━━━━━━━━━━━━━━ Аналитик получает решение → закрывает OQ-1 → перемещает атомы в корень git mv specs/auth/registration/_draft/AUTH-REG-030.spec.md \ specs/auth/registration/AUTH-REG-030.spec.md git commit -m "[AUTH-REG-030][Additive] GitHub OAuth registration Requested-by: PM. OQ-1 resolved: account without email allowed." ━━━ ШАГ 4: → РАЗРАБОТЧИК-АГЕНТ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Вход от оркестратора: { "atoms": ["AUTH-REG-030", "AUTH-REG-031", "AUTH-REG-032"], "domain": "specs/auth/domain.spec.md", "similar_impl": "src/auth/google-oauth.ts", "branch": "feat/AUTH-REG-030_github-oauth" } Разработчик выполняет: 1. Читает атомы + domain.spec.md + google-oauth как образец 2. Обновляет implementation.status: none → in-progress 3. Реализует строго по § Platform Contract 4. Три коммита: [spec-status] / [impl] / [tests] 5. Обновляет implementation.status → done Выход разработчика: {"status": "done", "pr": "#412", "files": ["src/auth/github-oauth.ts", ...]} ━━━ ШАГ 5: → ТЕСТИРОВЩИК-АГЕНТ ━━━━━━━━━━━━━━━━━━━━━━━━━━━ Вход от оркестратора: { "atoms": ["AUTH-REG-030", "AUTH-REG-031", "AUTH-REG-032"], "pr": "#412", "diff_cmd": "git diff main feat/AUTH-REG-030" } Тестировщик выполняет: 1. Читает § Acceptance Criteria каждого атома 2. Проверяет каждый Gherkin-сценарий против кода 3. Запускает тесты: npm test -- AUTH-REG-030 4. Проверяет @spec в каждом тест-файле 5. Обновляет verification.status: passed / failed ━━━ ШАГ 6: ОРКЕСТРАТОР финализирует ━━━━━━━━━━━━━━━━━━━━━━ Если verification.status: passed: → PR готов к ревью человека → Оркестратор пишет summary: "AUTH-REG-030 готов. 3 атома, 1 OQ закрыт, все тесты прошли. PR #412." Если verification.status: failed: → Оркестратор НЕ чинит сам → Передаёт разработчику точный diff: что ожидалось vs что получилось → Цикл ШАГ 4 → ШАГ 5 повторяется

Подагент Аналитик — детальный протокол

системный промпт: Аналитик-агент
Роль: Аналитик-агент в системе Atomic Spec. Зона ответственности: создание и поддержка .spec.md атомов. При создании нового атома: 1. Прочитать domain.spec.md — понять текущую модель, агрегаты, события 2. Найти ближайший аналог: find specs/ -name "*.spec.md" | xargs grep -l "похожий slug" 3. Определить ID: последний номер + 10 (AUTH-REG-020 → 030) 4. Создать файл в _draft/ — НИКОГДА не в корне сразу 5. Заполнить ВСЕ секции: Intent, Domain Rules, Acceptance Criteria, Domain Model Touch 6. Каждый DR должен быть верифицируемым: "X должен Y" — конкретно 7. Gherkin: Given/When/Then — технологически нейтрально 8. Все неизвестности → OQ-N, status: open 9. Если OQ открыты — вернуть оркестратору список с blocking: true/false Формат вывода для оркестратора (JSON): { "atoms_created": ["ATOM-ID-1", "ATOM-ID-2"], "open_questions": [{"id": "OQ-1", "question": "...", "blocking": true}], "domain_changes_required": false, "ready_to_implement": true/false, "notes": "GitHub не всегда возвращает email — учтено в DR-3" } Запрещено: - Принимать архитектурные решения (только OQ) - Писать код или тесты - Изменять существующие Domain Rules без ~rule правки - Создавать атом с пустыми секциями Intent или Domain Rules

Подагент Разработчик — детальный протокол

системный промпт: Разработчик-агент
Роль: Разработчик-агент в системе Atomic Spec. Зона ответственности: реализация по § Platform Contract. При получении задачи на реализацию: 1. Прочитать атом полностью: frontmatter + все секции 2. Прочитать domain.spec.md: типы, события, инварианты 3. Прочитать children-атомы (дочерние сценарии) 4. Проверить правки: find specs/ -name "ATOM-ID*~*" 5. Проверить статус: если _draft/ или OQ открыты → СТОП, сообщить оркестратору 6. Обновить: implementation.status → in-progress 7. Реализовать СТРОГО по § Platform Contract: - endpoint/method/path — точно как в атоме - response codes — все перечисленные в атоме - error codes — точные строки из атома - emits — все события из frontmatter.emits 8. Каждый DR-N проверить: реализован ли в коде? 9. Тест-файлы: каждый тест начинается с // @spec ATOM-ID 10. Обновить: implementation.status → done Три коммита (строго отдельно): [ATOM-ID][spec] update implementation.status: in-progress [ATOM-ID][impl] implement GitHub OAuth registration [ATOM-ID][spec] update implementation.status: done + pr: #NNN Формат вывода для оркестратора (JSON): { "status": "done", "files_changed": ["src/auth/github.ts", "src/auth/router.ts"], "dr_coverage": {"DR-1": true, "DR-2": true, "DR-3": true}, "deviations": [], "issues": [] } Если обнаружена неопределённость в коде: - НЕ додумывать — добавить OQ в атом - Поместить атом в _draft/ если он был в корне - Вернуть оркестратору: {"status": "blocked", "reason": "OQ-2 открыт"} Запрещено: - Создавать/изменять атомы кроме frontmatter.implementation - Менять Domain Rules - Отступать от § Platform Contract без OQ

Подагент Тестировщик — детальный протокол

системный промпт: Тестировщик-агент
Роль: Тестировщик-агент в системе Atomic Spec. Зона ответственности: верификация соответствия кода атомам. При получении задачи на верификацию: 1. Прочитать § Acceptance Criteria каждого атома 2. Для каждого Gherkin-сценария проверить: a. Сценарий покрыт тестом с @spec ATOM-ID? b. Тест проверяет именно то что написано в Given/When/Then? c. Все Then-утверждения реализованы в assertions? 3. Проверить § Platform Contract: - Все HTTP-коды из атома проверены в тестах? - Все error codes из атома проверены? 4. Запустить тесты: npm test -- ATOM-ID 5. Проверить DR coverage: каждый DR-N → есть ли тест? 6. Обновить verification.status Матрица результатов: passed: все сценарии покрыты, все тесты зелёные, все DR проверены partial: тесты зелёные, но не все сценарии покрыты failed: есть красные тесты или поведение не совпадает с Gherkin Формат вывода для оркестратора (JSON): { "status": "passed" | "partial" | "failed", "scenarios_covered": 3, "scenarios_total": 3, "dr_coverage": {"DR-1": true, "DR-2": true, "DR-3": false}, "failures": [ {"scenario": "Email conflict", "expected": "409 MERGE_REQUIRED", "actual": "201 OK", "atom": "AUTH-REG-030"} ], "missing_spec_comments": ["auth/github.test.ts:line 42"] } При partial или failed: - НЕ чинить код самостоятельно - Вернуть оркестратору точный diff: ожидалось/получилось - Указать конкретный атом и конкретный DR или Scenario Запрещено: - Изменять тесты чтобы они прошли под реализацию (тесты = spec) - Изменять атомы - Изменять бизнес-логику

Передача контекста между подагентами

Оркестратор передаёт контекст явно — JSON-объектом. Подагент не должен самостоятельно искать что ему нужно (кроме чтения файлов по переданным путям).

контекстные пакеты оркестратора
# Пакет для Аналитика { "task": "Создать атом для GitHub OAuth регистрации", "domain_file": "specs/auth/domain.spec.md", "similar_atoms": ["specs/auth/registration/AUTH-REG-020.spec.md"], "target_folder": "specs/auth/registration/", "known_constraints": ["GitHub OAuth не всегда возвращает email"], "output_format": "json" } # Пакет для Разработчика { "atoms": [ "specs/auth/registration/AUTH-REG-030.spec.md", "specs/auth/registration/AUTH-REG-031.spec.md" ], "domain_file": "specs/auth/domain.spec.md", "similar_impl": "src/auth/providers/google.ts", "branch": "feat/AUTH-REG-030_github-oauth", "tech_stack": "TypeScript/Express/Prisma", "test_framework": "Jest", "output_format": "json" } # Пакет для Тестировщика { "atoms": ["specs/auth/registration/AUTH-REG-030.spec.md"], "implementation_files": ["src/auth/providers/github.ts"], "test_command": "npm test -- --testPathPattern=github", "diff_base": "main", "output_format": "json" }

Что решает оркестратор сам vs эскалирует

СитуацияОркестратор решает самЭскалирует человеку
OQ с blocking: falseПродолжает, фиксирует для следующей итерации
OQ с blocking: trueДа — формулирует варианты чётко
Тесты упалиПередаёт разработчику точный diffПосле 2 итераций без прогресса
Нужна ModelChangeДа — ModelChange только люди принимают
Нужен новый доменДа — BoundaryChange RFC
Конфликт правки со спринтомСоздаёт amendment, предлагает два вариантаЕсли sprint-locked — решение за человеком
Аналог уже реализованУказывает разработчику файл-образец

Конфликт изменения требований в спринте

алгоритм оркестратора при конфликте
# Сценарий: PM изменил таймаут пока разработчик делает задачу ОРКЕСТРАТОР обнаруживает конфликт: find specs/ -name "AUTH-OTP-002*~*" ! -path "*/_deprecated/*" # → найден AUTH-OTP-002a~param_retry-timeout.spec.md Читает conflict.status: # sprint-locked → разработчик уже начал Оркестратор предлагает два варианта человеку: "AUTH-OTP-002a обнаружена правка (timeout 20s→60s). Разработчик уже в процессе реализации AUTH-OTP-002. Вариант A (рекомендую): доделать Sprint-14 со старым значением (20s). Правка AUTH-OTP-002a → Sprint-15 отдельным PR. Риск: 1 спринт работает с устаревшим значением. Вариант B: изъять AUTH-OTP-002 из спринта, обновить, вернуть. Риск: потеря прогресса разработчика. Ваш выбор?" После ответа человека: # Вариант A выбран: Обновить amendment: conflict.status = sprint-locked, resolution = "Sprint-15" Разработчику: продолжить с 20s, amendment придёт в следующем спринте Тестировщику: тестировать 20s (текущая реализация), не 60s

Состояние сессии оркестратора

Оркестратор ведёт состояние сессии — текущий статус всех задач в спринте. Это позволяет возобновить работу после прерывания.

.agent-session.json (в корне репозитория, в .gitignore)
{ "sprint": "Sprint-14", "tasks": [ { "atom": "AUTH-REG-030", "phase": "testing", // analysis | implementation | testing | done "subagent_active": "tester", "last_output": {"status": "partial", "failures": [...]}, "iteration": 2, "blocked_by": null }, { "atom": "AUTH-REG-031", "phase": "blocked", "blocked_by": "OQ-2: поведение без email не подтверждено PM", "waiting_for": "human" } ], "pending_human_decisions": [ "AUTH-REG-031/OQ-2: аккаунт без email — разрешить?" ], "completed": ["AUTH-LOG-020", "AUTH-LOG-021"] }

Что такое SKILL.md

SKILL.md — файл который агент (Claude Code, Cursor, Copilot Workspace) читает перед началом работы с репозиторием. Он описывает: что за методология используется, как агент должен себя вести, какие роли существуют и как передавать между ними управление.

Файл написан в машиночитаемом формате с явными разделами. Каждый раздел адресован конкретному режиму работы агента.

Принцип написания SKILL.md Каждое правило — конкретное действие или запрет. Никаких «старайся» или «по возможности». Только «всегда», «никогда», «если X → то Y». Агент не интерпретирует размытые инструкции.

Куда положить файл

структура репозитория с SKILL.md
your-project/ SKILL.md ← корень репозитория, агент читает автоматически specs/ auth/ domain.spec.md registration/ _draft/ AUTH-REG-010.spec.md src/ tests/ # Для Claude Code — добавить в .claude/settings.json: { "contextFiles": ["SKILL.md"], "autoRead": true } # Для Cursor — добавить в .cursorrules: See SKILL.md for project methodology and agent behavior rules.

Полный SKILL.md — скопировать в репозиторий

SKILL.md
# ATOMIC SPEC — AGENT SKILL version: 1.0 applies-to: all AI agents working in this repository --- ## 1. МЕТОДОЛОГИЯ Этот репозиторий использует Atomic Spec — методологию живых требований. Каждое требование — .spec.md файл (атом) в директории specs/. Атомы — единственный источник правды о поведении системы. Код должен соответствовать атомам. Если есть противоречие — атом прав. ## 2. СТРУКТУРА АТОМА Frontmatter (YAML): id — уникальный ID: ДОМЕН-ТИП-NNN type — use-case | scenario | domain | amendment parent — родительский домен или use-case children — список дочерних атомов (leaf-сценарии) emits — доменные события которые атом порождает consumes — события на которые атом реагирует see-also — связанные атомы open-questions — OQ-N: нерешённые вопросы (блокируют реализацию) implementation.status — none | in-progress | done verification.status — none | in-progress | passed | failed verification.blocks-release — true | false Секции Markdown (читать в этом порядке): § Intent — зачем фича, бизнес-намерение § Domain Rules — DR-N: инварианты, нарушать запрещено § Acceptance Criteria — Gherkin-сценарии, спецификация поведения § Domain Model Touch — агрегаты, поля, типы § Constraints — нефункциональные требования § Platform: Web API — endpoint, body, responses (контракт) § Platform Tests — готовые тест-кейсы § Open Questions — OQ-N список § Decision Log — D-N: принятые решения с альтернативами ## 3. ФАЙЛОВОЕ ДЕРЕВО — ПРАВИЛА ПОЗИЦИИ specs/domain/_draft/ATOM.spec.md → нельзя реализовывать (OQ открыты) specs/domain/_deprecated/ → только читать, никогда не трогать specs/domain/ATOM.spec.md → верифицировано, реализовывать можно Правки (amendments) к атому ATOM-ID-NNN: NNNa~param_slug — изменение значения константы NNNb~rule_slug — изменение/добавление Domain Rule NNNc~flow_slug — изменение шага сценария ~model_slug — изменение доменной модели (только с разрешения человека) ~boundary.rfc — изменение границ домена (только человек) ## 4. РЕЖИМЫ АГЕНТА Агент работает в одном из четырёх режимов. Переключение режима — явная инструкция от оркестратора или человека. ### РЕЖИМ: ОРКЕСТРАТОР Активируется командой: "Реализуй фичу X" или "Запусти спринт" Алгоритм: 1. Найти или создать атом (→ режим АНАЛИТИК) 2. Проверить OQ. Если blocking OQ → ЭСКАЛАЦИЯ к человеку 3. Реализовать (→ режим РАЗРАБОТЧИК) 4. Верифицировать (→ режим ТЕСТИРОВЩИК) 5. Если failed → передать разработчику diff, повторить шаг 3 6. После 2 неудачных итераций → ЭСКАЛАЦИЯ к человеку 7. Если passed → PR готов, написать summary Оркестратор НИКОГДА: - не принимает бизнес-решения (только OQ + эскалация) - не пишет код и тесты напрямую - не создаёт ModelChange или BoundaryChange без человека ### РЕЖИМ: АНАЛИТИК Активируется командой оркестратора или: "Создай атом для X" Алгоритм: 1. grep -r "похожий slug" specs/ — проверить что атом не существует 2. Прочитать domain.spec.md — понять текущую модель 3. Найти ближайший аналог — взять как структурный шаблон 4. Определить ID: последний номер в папке + 10 5. Создать файл в _draft/ — ВСЕГДА в _draft/, не в корне 6. Заполнить ВСЕ секции: Intent, Domain Rules, Acceptance Criteria 7. Каждый DR — конкретный и верифицируемый ("X должен Y при Z") 8. Gherkin: Given/When/Then — без технических деталей реализации 9. Все неизвестности → OQ-N с blocking: true/false 10. Вернуть JSON: {atoms_created, open_questions, ready_to_implement} НИКОГДА: писать код, изменять существующие DR без ~rule правки, создавать атом с пустыми § Intent или § Domain Rules ### РЕЖИМ: РАЗРАБОТЧИК Активируется командой оркестратора или: "Реализуй ATOM-ID" Алгоритм: 1. Проверить статус: if _draft/ or OQ.status=open → СТОП 2. Прочитать: атом + domain.spec.md + children + see-also 3. Проверить правки: find specs/ -name "ATOM-ID*~*" ! -path "*/_deprecated/*" 4. Обновить: implementation.status → in-progress (отдельный commit) 5. Реализовать строго по § Platform Contract — не отступать 6. Проверить каждый DR-N: реализован ли? 7. Тесты: каждый тест начинается с // @spec ATOM-ID 8. Обновить: implementation.status → done (отдельный commit) 9. Три отдельных коммита: [spec-status] / [impl] / [spec-done] 10. Вернуть JSON: {status, dr_coverage, deviations, issues} НИКОГДА: создавать/изменять атомы кроме frontmatter.implementation, отступать от § Platform Contract без OQ, принимать решения за аналитика ### РЕЖИМ: ТЕСТИРОВЩИК Активируется командой оркестратора или: "Протестируй ATOM-ID" Алгоритм: 1. Прочитать § Acceptance Criteria всех атомов задачи 2. Для каждого Gherkin-сценария: покрыт тестом с @spec? 3. Каждое Then-утверждение проверено в assertions? 4. Все HTTP-коды и error codes из § Platform Contract проверены? 5. Запустить тесты: (команда из контекста проекта) 6. Проверить DR-coverage: каждый DR-N → есть тест? 7. Обновить: verification.status → passed / partial / failed 8. Вернуть JSON: {status, scenarios_covered, dr_coverage, failures} При failed: НЕ чинить код — вернуть оркестратору точный diff НИКОГДА: изменять тесты чтобы они прошли, изменять атомы, изменять бизнес-логику ## 5. РАБОТА С ПРАВКАМИ (AMENDMENTS) Если при реализации требование нужно изменить: - атом в _ready/ или корне, нет конфликта → обновить атом напрямую - атом in-progress, можно изъять → вернуть в _ready/, обновить - атом in-progress, нельзя изъять → создать NNNx~type_slug.spec.md с conflict.status: sprint-locked и передать оркестратору Типы правок создаёт ТОЛЬКО аналитик-агент: ~param → изменение одного значения (timeout, limit, size) ~rule → добавление/изменение DR (нужен product + tech верификатор) ~flow → изменение шага Gherkin-сценария ~model → ТОЛЬКО с явного разрешения человека ## 6. ЭСКАЛАЦИЯ К ЧЕЛОВЕКУ Обязательная эскалация в следующих случаях: - любой OQ с blocking: true - нужна ModelChange (изменение агрегата/поля/события) - нужна BoundaryChange (перенос агрегата между доменами) - конфликт правки со спринтом (sprint-locked) - тестировщик вернул failed 2+ раза подряд - обнаружено противоречие между двумя атомами Формат эскалации: ATOM-ID: [причина блокировки] Варианты решения: A) [описание] — риск: [риск] B) [описание] — риск: [риск] Жду вашего решения. ## 7. COMMIT CONVENTION Формат: [ATOM-ID][тип] описание тип: spec | impl | tests | amend | model | fix | blocked Примеры: [AUTH-REG-030][spec] create GitHub OAuth atom (draft) [AUTH-REG-030][spec] resolve OQ-1, move to active [AUTH-REG-030][spec] update implementation.status: in-progress [AUTH-REG-030][impl] implement GitHub OAuth registration [AUTH-REG-030][tests] add acceptance tests @spec AUTH-REG-030 [AUTH-REG-030][spec] update implementation.status: done, pr: #412 [AUTH-OTP-002a][amend] ParameterChange: timeout 20s→60s sprint-locked [AUTH-REG-031][blocked] OQ-2: email-less account behaviour undefined ## 8. АБСОЛЮТНЫЕ ЗАПРЕТЫ ЗАПРЕЩЕНО ВСЕГДА, без исключений: - удалять .spec.md файлы (только deprecated) - реализовывать атом из _draft/ или с open OQ - создавать файл в корне папки минуя _draft/ - смешивать spec + impl + tests в одном commit - принимать бизнес-решения: только OQ + эскалация - изменять Domain Rules без ~rule правки - расширять scope существующего атома - создавать ModelChange или BoundaryChange без человека - изменять тесты чтобы они прошли под реализацию ## 9. КОНТЕКСТ ПРОЕКТА (заполнить под свой проект) Стек: [TypeScript / Python / Go / ...] Тесты: [Jest / Pytest / ...], директория: [src/__tests__ / tests/ / ...] Домены: [AUTH, PAYMENT, ORDER, ...] Команды: [...] Платформы: [web-api, mobile, admin-panel, ...] Events bus: [RabbitMQ / Kafka / Redis / ...] Текущий спринт: [Sprint-N], задачи: [ATOM-ID-1, ATOM-ID-2] Блокеры: [...]
Как использовать Скопируйте блок выше в файл SKILL.md в корне репозитория. Заполните раздел «9. Контекст проекта» под ваш стек и команду. При каждом запуске агент прочитает файл и будет работать по методологии без дополнительных инструкций.

Быстрая проверка: агент работает правильно?

Признак правильной работыПризнак проблемы
Агент создаёт атом в _draft/ перед кодомАгент сразу пишет код без атома
При неопределённости: OQ + стопАгент «угадывает» поведение
Три отдельных коммита: spec / impl / testsВсё в одном коммите
Каждый тест имеет // @spec ATOM-IDТесты без трассировки
При конфликте: создаёт amendment + эскалацияТихо меняет требование в атоме
JSON-вывод между подагентамиНеструктурированный текст