Зачем это всё
Как зачастую бывает. Приходит ТЗ или описание задачи, которую необходимо реализовать. Если ТЗ нет, то приходится додумывать самому. Если есть ТЗ, то это уже хорошо, есть с чем работать, но зачастую есть нюанс: ТЗ на десять страниц, местами противоречит само себе. Через неделю работы выясняется, что заказчик на самом деле имел в виду не это, а совсем другое (или ты не понял, что он имел в виду), и то, что ты уже успел сделать, идёт в помойку. Через месяц заказчик говорит "всё очень круто, но надо переделать", и выясняется, что вся логика завязана на то самое первое ТЗ, которое ты уже забыл.
Техническое задание умирает. Оно лежит где-то в почте или тикет-системе, отдельно от кода, отдельно от git или хранилища. Разработчики, которые пришли позже, читают код и гадают, почему вот тут обращение идёт именно через регистр, а не через документ, а здесь используется реквизит с типом число, а не булево.
SDD (spec-driven development) - это попытка изменить этот процесс. ТЗ становится не протухшим документом в почте, а спецификацией (или набором спецификаций), являющейся частью репозитория и, как следствие, исходного кода. Оно лежит рядом с кодом, версионируется вместе с ним и, главное, пишется до кода. Не "надо переписать обработку выгрузки, потом опишем, что сделали", а "сначала фиксируем целевое поведение в спецификации, потом реализуем, а спецификация идёт в git".
Что такое spec-driven development
Это подход, при котором разработка начинается не с кода, а с описания целевого поведения системы. Спецификация определяет, КОГДА, ЧТО и КАК система должна делать. Это текст рядом с кодом в формате Markdown, который лежит в git.
В 1С по большей части исторически сложилось так:
- конфигуратором открываем объект
- проектируем логику в голове
- что-то пишем в комментарии модуля
- кладем результат в git или хранилище.
Спецификации нет, описания нет. Логика живёт в голове автора и в комментариях, которые если и есть, то их почти никто никогда не обновляет.
Когда спецификация есть в репозитории, происходят три приятные вещи:
- Ревью кода становится осмысленным. Ревьюер сначала читает спецификацию "что должно произойти", потом смотрит "что на самом деле произошло" в коде. Если они разошлись, баг виден сразу.
- Скрытые моменты всплывают и обсуждаются заранее. Пока пишешь спецификацию, всплывает то, о чем не подумал заказчик. Не очень хочется понять что что-то было упущено во время тестирования готовой доработки.
- Новый разработчик входит в тему быстрее. Прочитать спецификацию на "проведение документа со снятием резерва" - это полчаса. Понять то же самое, читая модуль объекта, гораздо дольше (если вообще возможно). К ИИ-Агенту это тоже относится!
ВАЖНО: спецификация не заменяет понимание архитектуры. Спецификация — это не "напиши модуль на 1С автоматически". Это способ зафиксировать целевое поведение системы.
От вайб-кодинга это отличается принципиально. Вайб-кодинг - это когда кидаешь агенту промпт в духе "сделай красиво", он что-то генерит, тебе в целом нравится, принимаешь. Проблема в том, что через неделю не вспомнишь, почему код именно такой, а агент тем более не помнит, т.к. долговременной памяти у него как таковой нет.

Спецификация же фиксирует контракт до реализации. Ты сначала утверждаешь что должно быть сделано, агент предлагает спеку, ты её читаешь и правишь, и только потом он пишет код. Артефакт остаётся в репозитории.
Сам по себе SDD - подход, не привязанный к конкретному инструменту. Но если скрестить его с LLM, получается интересно: ИИ-агент сам изучает кодовую базу, пишет спецификацию, а затем по ней же реализует код. Роль разработчика смещается с "пишу код" на "пишу как должна работать система".
OpenSpec и что это за зверь
OpenSpec - это opensource-фреймворк для SDD-разработки с помощью LLM. Он даёт возможность поддерживать при разработке цикл "Исследование -> Спецификация -> Реализация -> Архивация" с помощью команд, которые становятся доступными после инициализации OpenSpec в проекте. Команд немного, и это плюс.
Работает он по следующей логике. В проекте есть каталог openspec/. В нём спецификации. Одна спецификация - это одна фича, одно исправление, одна доработка. Каждая спецификация представляет из себя каталог с несколькими файлами Markdown. Когда фича готова и залита в основную ветку или добавлена в конфигурацию, спецификацию архивируют. Она остаётся в истории, но не мешает в активном списке.
Команды, на которых всё держится: explore, propose, apply и archive. Разберём их на живом кейсе.
Готовим исходники: выгрузка конфигурации
OpenSpec предполагает, что конфигурация лежит в репозитории в исходниках. Без этого подход теряет половину смысла: спецификация отдельно, код отдельно. Нужны исходники.
ВАЖНО: При использовании хранилища 1С, спецификацию рядом с кодом хранить не получится, для неё надо создать git-репозиторий.
Создадим папку проекта "onec-os-example" в любом удобном месте.
Исходники выгрузим средствами конфигуратора: "Конфигурация -> Выгрузить конфигурацию в файлы" в папку "src", находящуюся в папке проекта. Получаем каталог, в котором содержатся все данные конфигурации в "родном" для 1С формате.
Сквозной кейс: от спецификации до архивации
Постановка
Дано: некая конфигурация, в которой есть интеграция с внешней системой. Точно знаем, что есть функция, которая размещает системный комментарий в объекте внешней системы под названием "служебная записка" по номеру этого объекта. Мы хотим реализовать функцию, которая позволит разместить один и тот же комментарий сразу в нескольких объектах. API со стороны внешней системы это поддерживает, пример вызова есть. Ничего сложного, чисто как учебный кейс. Цель - пройти весь цикл через команды OpenSpec.
Шаг 0: подготовка
Установим OpenSpec в папку проекта. В powershell (или bash) пишем:
npm install -g @fission-ai/openspec@latest
Если node.js не установлен, это можно сделать, скачав дистрибутив с сайта.
После установки необходимо инициализировать OpenSpec. Сделаем это:
openspec init
В появившемся окне надо нажать Enter и выбрать одну или несколько систем которые вы используете:

Я использую OpenCode, поэтому выбираю его, найдя в списке и нажав Пробел.

Затем нажимаю Enter и возвращаюсь в командную строку.
После инициализации в проекта появляется папка openspec, а внутри неё создаются подкаталоги: changes (активные спецификации) и specs (постоянно живущие спецификации), а потом появятся и archive (завершённые изменения).
Открываем Opencode в рабочей папке (если используем CLI-режим), или открываем в Opencode GUI папку нашего проекта и создаем новую сессию. Я буду использовать Opencode GUI и модель Kimi K3.
Шаг 1. Команда explore
explore - это команда, которая позволяет "изучить" кодовую базу, "подумать" над изменениями прежде чем даже сформулировать их. Если в проекте уже есть какие-то изменения OpenSpec, то по ним также производится поиск.
Вводим в окне ввода OpenCode /opsx-explore и дальше сразу пишем:
В системе есть функция размещения комментария в служебной записке по её номеру. надо создать аналогичную функцию, которая размещает один и тот же комментарий в несколько служебных записок. Пример тела запроса для размещения комментария: `{"memo_ids": [3239781, 3238228, 3213925], "body": "test"}`, URL: `/memos/comments/batch_post`
Агент работает 4 минуты, находит 2 функции в модуле оСлужебные и определяет их контракт:
- РазместитьКомментарийВСЗ(НомерСЗ, ТекстКомментария, МассивПолучателей)
- РазместитьКомментарийВСЗ_Новая(НомерСЗ, ТекстКомментария, МассивПолучателей)
Агент находит расхождение: в имеющихся функциях есть дополнительные параметры "auto_include_subst" и "receiver_ids", которые отсутствуют в новом контракте. Помните я писал о том что всплывают всякие непредусмотренные изначально вещи? Это они и есть. В данном случае мы знаем, что этих параметров нет в API, и, несмотря на то что агент предложил сделать запрос с помощью curl, чтобы определить поддерживаются они или нет, ответим ему что этих параметров нет.
Агент спрашивает, возможен ли частичный успех выполнения операции на стороне внешней системы. Мы знаем, что система обрабатывает вызов целиком: или всё удалось, или не удалось ничего. Так ему и ответим.
Также он спросил как лучше назвать функцию, предложив два варианта: РазместитьКомментарийВСЗПакетом или РазместитьКомментарийВНесколькоСЗ.
Отвечаем ему:
Дополнительных параметров нет. Функция на стороне внешней системы обрабатывает либо всё, либо ничего. Функцию называем РазместитьКомментарийВСЗПакетом.
Агент подумал около минуты и сказал, что ему всё понятно:
Хотите, чтобы я оформил это как change proposal в OpenSpec (proposal.md + tasks.md + delta-spec на «комментарии в СЗ»)? Это захват решения как артефакты, не реализация кода — позволено в explore mode.
Отвечаем "да, хотим", и агент вызывает команду propose.
Шаг 2. Команда propose
propose — это команда, которая генерирует структуру спецификации под новую доработку. На вход мы или сам агент подает то что нужно сделать, а OpenSpec каталог под новое изменение (change). В нашем случае change называется "add-batch-comment-post". Команду мы вручную не вводили, за нас это сделал агент. Вручную это можно сделать, введя команду:
/opsx-propose
В proposal.md живёт смысл доработки на человеческом языке: зачем, что мы меняем, какая мотивация. Это читает бизнес-аналитик, заказчик, любой человек. У нас получилось примерно так:
## WHY
Требуется размещать один и тот же комментарий сразу в несколько служебных записок (СЗ) за один HTTP-вызов к ПМ.
Сейчас в `CommonModule.оСлужебные` есть только функция `РазместитьКомментарийВСЗ` (и более новая `РазместитьКомментарийВСЗ_Новая`) — обе работают по одной СЗ. При массовом уведомлении (например, по списку процессов увольнения/отпуска) приходится вызывать их в цикле, что порождает много сетевых запросов и не использует существующий batch-эндпоинт ПМ `/memos/comments/batch_post`.
design.md — техническое описание на уровне архитектуры. Какие именно объекты создаются, чем отличаются, как зависят от существующих. У нас примерно так:
Общий модуль `оСлужебные` уже умеет размещать одиночный комментарий в СЗ через `РазместитьКомментарийВСЗ` и `РазместитьКомментарийВСЗ_Новая` (`src/CommonModules/оСлужебные/Ext/Module.bsl:670-738`). Внешняя система ПМ предоставляет отдельный batch-эндпоинт `/memos/comments/batch_post` для пакетной публикации одного и того же комментария сразу в несколько СЗ. Существующие функции такого вызова не делают; потребители вынуждены вызывать их в цикле.
Контракт batch-эндпоинта зафиксирован пользователем:
- URL: `POST {base}/memos/comments/batch_post`
- Тело: `{"memo_ids": [<int>, ...], "body": "<string>"}`
- Дополнительные поля (`auto_include_subst`, `receiver_ids`) не передаются.
- Обработка на стороне ПМ атомарна — либо комментарий размещён во все переданные СЗ, ни в одну.
tasks.md — чек-лист шагов реализации в стиле `- [ ]`. Потом пригодится, чтобы сверить, что было сделано. У нас получилось 13 задач в 4 разделах.
specs/spec.md — это уже детальная спецификация по конкретному объекту. В нашем случае - спецификация на поведение системы, сценарии успешного выполнения, появления ошибок, повтора вызова и т.п:
#### Scenario: Успешное пакетное размещение комментария
- **WHEN** вызывается `РазместитьКомментарийВСЗПакетом` с непустым массивом номеров `[3239781, 3238228, 3213925]` и непустым текстом `"test"` и внешняя система отвечает HTTP 200
- **THEN** функция выполняет ровно один `POST /memos/comments/batch_post` с телом `{"memo_ids": [3239781, 3238228, 3213925], "body": "test"}` и возвращает структуру `{"Ошибка": Ложь, "ОписаниеОшибки": "", "Ответ": "<текст ответа внешней системы>"}`
#### Scenario: Внешняя система вернула ошибку — повтор попытки
- **WHEN** вызывается `РазместитьКомментарийВСЗПакетом` с корректными аргументами и внешняя система три раза подряд отвечает HTTP-кодом, отличным от 200
- **THEN** функция делает до 3 попыток HTTP-вызова с задержкой 3 секунды между ними (через процедуру `Задержка`) и возвращает структуру `{"Ошибка": Истина, "ОписаниеОшибки": "<текст ответа при последней попытке>", "Ответ": ""}`
#### Scenario: Успех после первой неудачной попытки
- **WHEN** первая HTTP-попытка возвращает не-200, а вторая — HTTP 200
- **THEN** функция прекращает повтор, возвращает структуру `{"Ошибка": Ложь, "ОписаниеОшибки": "", "Ответ": "<текст ответа второй попытки>"}` и больше HTTP-вызовов не делает
Это всё можно прочитать, внести какие-то изменения (если необходимо) и запустить реализацию.
Мы видим что в спецификации написано что функция делает до трех попыток HTTP-вызова, хотя мы этого напрямую не просили. Пока оставим.
Агент говорит нам:
Мы в explore mode — реализацию кода не пишу. Когда захотите приступить — выйдите из explore mode и запустите /opsx-apply (или скажите «реализуй»), и я пройду по tasks.md.
Шаг 3. Команда apply
Когда спецификация готова, ревью прошла и мы готовы реализовывать - вызываем команду `apply`. Эта команда "применяет" спецификацию.
Выполним её:
/opsx-apply
Модель Kimi K3 думала 8 минут и написала в файле общего модуля функцию:
Функция РазместитьКомментарийВСЗПакетом(МассивНомеровСЗ, ТекстКомментария) Экспорт
РезультатФункции = Новый Структура;
РезультатФункции.Вставить("Ошибка", Ложь);
РезультатФункции.Вставить("ОписаниеОшибки", "");
РезультатФункции.Вставить("Ответ", "");
Если НЕ ЗначениеЗаполнено(ТекстКомментария) Тогда
РезультатФункции.Ошибка = Истина;
РезультатФункции.ОписаниеОшибки = "Не заполнен текст комментария";
Возврат РезультатФункции;
КонецЕсли;
Если МассивНомеровСЗ = Неопределено ИЛИ МассивНомеровСЗ.Количество() = 0 Тогда
РезультатФункции.Ошибка = Истина;
РезультатФункции.ОписаниеОшибки = "Не переданы номера служебных записок";
Возврат РезультатФункции;
КонецЕсли;
URL = ПолучитьБазовыйURLПМ() + "/memos/comments/batch_post";
ТелоЗапроса = Новый Структура;
ТелоЗапроса.Вставить("memo_ids", МассивНомеровСЗ);
ТелоЗапроса.Вставить("body", ТекстКомментария);
КоличествоПопыток = 3;
НомерПопытки = 1;
Пока НомерПопытки <= КоличествоПопыток Цикл
Попытка
Результат = КоннекторHTTP.Post(URL, ТелоЗапроса);
Исключение
Результат = Неопределено;
РезультатФункции.ОписаниеОшибки = ОписаниеОшибки();
КонецПопытки;
Если Результат <> Неопределено И Результат.КодСостояния = 200 Тогда
РезультатФункции.Ответ = КоннекторHTTP.КакТекст(Результат);
Возврат РезультатФункции;
КонецЕсли;
Если Результат <> Неопределено Тогда
РезультатФункции.ОписаниеОшибки = КоннекторHTTP.КакТекст(Результат);
КонецЕсли;
НомерПопытки = НомерПопытки + 1;
Задержка(3);
КонецЦикла;
РезультатФункции.Ошибка = Истина;
Возврат РезультатФункции;
КонецФункции
Теперь можно загрузить конфигурацию из файлов (или вручную вставить текст функции) и протестировать её.
Если появились ошибки, то пишем агенту что не так и он работает дальше.
Например, здесь используются попытки с паузой в 3 секунды, о которых мы не просили, но которые агент взял из функции с припиской "_Новая", и подтянул оттуда же баг: даже если у нас последняя попытка, то 3 секунды всё равно ждём. Агент честно сообщил об этом в ходе реализации, но решил оставить так, потому что так указано в "функции-шаблоне".
Скажем ему что нам не нужны попытки выполнения:
Попытки выполнения не нужны, нужна только одна, без задержек.
Агент говорит "сейчас отредактирую функцию, а затем поправлю файлы спецификации".
4 минуты он работал и действительно это сделал:

Все файлы спецификации были отредактированы в соответствии с измененными требованиями, а код функции приведен в соответствие этим требованиям. На самом деле он сделал наоборот, сначала код, а потом требования, но в рамках одного изменения это кажется допустимым.
Функцию забрали в конфигурацию и она действительно работает.
Одна задача из tasks.md (фиксация в репозитории) не была выполнена, о чем нам агент и сообщил.
Шаг 4. Команда archive
Функция готова, протестирована, код помещен в хранилище или репозиторий, тикет закрыт - спецификацию надо заархивировать.
Команда archive переносит изменение из changes и/или specs в openspec/archive/. После этого в активном индексе ничего не мешает, но архив остается - он лежит, доступен через explore, и при необходимости из него можно достать всю историю, почему было принято именно так.
В нашем кейсе после того как функцию протестировали, запускаем команду:
/opsx-archive
Спецификация переезжает в archive:

Если через какое-то время появится необходимость доработать функцию, будет создана новая спецификация, которая будет ссылаться на старую архивную. Цепочка решений сохраняется.
Ну и зачем, если можно просто написать эту функцию руками?
Главное и основное - это Агентная разработка. ИИ-агент при использовании OpenSpec не "лепит что попало", а следует правилам фреймворка. Это более быстрый и точный путь решения задач.
Побочный эффект - когда есть спецификации, по ним сразу и нам, и агенту понятно что, зачем и почему было сделано. Без них бы мы гадали почему сделано именно так, а не иначе.
Где SDD бесит:
- На маленьких задачах, например когда изменение в одной-двух строках кода, и точно известно что надо сделать, спецификация выглядит как забивание гвоздей микроскопом. Проще и быстрее без неё, но и ИИ-агента тут привлекать наверное не стоит.
- Если команда больше одного человека, все должны использовать SDD. Если SDD использует только один человек, а остальные игнорят, то это не сработает.
Вместо вывода
Я попытался рассказать и показать, как работает SDD в 1С на примере фреймворка OpenSpec и OpenCode на маленькой реальной задаче, где мы знали только то, что функция существует и нам нужен был её аналог.
100% можно навесить на OpenCode MCP-серверов, дополнительных скиллов, линтеров и прочего, и это всё станет быстрее, круче, контекстнее, но это тема других статей, которых и так немало.
Вступайте в нашу телеграмм-группу Инфостарт