@zurab/spec-first
v0.14.1
Published
Spec-First — CLI и библиотека для управления product passports (constitution + specs + US/FR/AC) поверх @zurab/mdmanager
Readme
@zurab/spec-first
CLI и библиотека для управления product passports на базе @zurab/mdmanager.
История изменений: CHANGELOG.md.
Spec-First вводит модель Product → Constitution → Specs → User Stories → Functional Requirements → Acceptance Criteria поверх markdown с HTML-маркерами. Каждый AC обязан иметь runner — runner превращает критерий приёмки в исполняемый тест, статус (✅/❌) автоматически попадает в собранный паспорт.
С версии 0.14.0 доступен второй режим работы — goal-mode: hypothesis-driven цели с персистентным контекстом (principles, bashrunner-чек-лист, dependent-files, related-ARDs, deferred-tasks). Подходит для исследовательской фазы перед формализацией спек. См. раздел Goal-mode.
Установка / запуск
# Через npx (рекомендованный режим публикации)
npx -p @zurab/spec-first spec-first --help
# Локально в монорепо
node tools/spec-first/dist/cli/spec-first.js --help@zurab/mdmanager — peerDependency. Устанавливается отдельно потребителем.
Two-level конфигурация
В одном workspace N продуктов. Конфиги двухуровневые:
| Уровень | Файл | Назначение |
|---|---|---|
| Workspace | .spec-first.json (root) | Где искать продукты, общие runners (global/extend) |
| Продукт | <product>/.spec-first.product.json | Имя/title, пути к constitution/template, локальные runners |
Discovery: spec-first ищет root config walk-up от cwd; затем сканирует passportsDir рекурсивно — продукт = директория, содержащая productConfigPattern (.spec-first.product.json по умолчанию).
Пример root .spec-first.json
{
"passportsDir": "docs/product-passports/products",
"productConfigPattern": ".spec-first.product.json",
"discoverBy": "config-file",
"globalRunners": [],
"extendRunners": []
}Пример per-product .spec-first.product.json
{
"id": "billing",
"title": "Биллинг",
"constitution": "constitution.md",
"passportTemplate": ".template.md",
"passportOutput": ".product.passport.md",
"specsDir": "docs/specs",
"specFilePattern": "*.spec-first.ai.md",
"specTemplate": "spec.template.md",
"runners": []
}Все пути в per-product config резолвятся относительно директории продукта. Минимальный валидный конфиг — { "id": "billing" }.
Hooks (post-runner хуки)
Поле hooks в per-product config — объект с именованными триггерами. Каждый триггер содержит список CLI-команд, которые выполняются автоматически в нужный момент цикла реализации. Команды запускаются через sh -c из корня workspace (там где лежит .spec-first.json).
{
"id": "billing",
"hooks": {
"after-close-ac": ["pnpm nx lint billing", "pnpm nx test billing"],
"after-close-all": ["pnpm nx build billing && echo 'Готово!'"]
}
}Триггеры
| Триггер | Когда срабатывает | Env-переменные |
|---|---|---|
| after-close-ac | После runner PASS при close ac | SPEC_FIRST_HOOK, SPEC_FIRST_PRODUCT, SPEC_FIRST_AC_ID |
| after-close-all | После успешного завершения close all | SPEC_FIRST_HOOK, SPEC_FIRST_PRODUCT, SPEC_FIRST_CLOSED_ACS (CSV) |
Важно: after-close-ac не запускается внутри close all — только after-close-all. Это позволяет запускать тяжёлые команды (build, lint) один раз в конце батча, а не после каждого AC.
При запуске run --product (re-validation без смены статусов) хуки не запускаются.
Семантика fail
Если любая команда хука завершается с ненулевым кодом:
close ac— AC не закрывается (остаётсяopen), exit 1.close all— команда завершается с exit 1.
Команды выполняются последовательно. Первый fail останавливает всё.
Пример: автоматический lint после каждого AC
# Добавить хук
spec-first set hooks --product billing --trigger after-close-ac --add "pnpm nx lint billing"
# Теперь при каждом 'close ac' автоматически запустится lint:
spec-first close ac SPEC-001:AC-003
# [hook] > pnpm nx lint billing
# [hook] ✔ OK
# ✓ SPEC-001:AC-003: закрыт (1240ms)Пример: build + уведомление после закрытия всех задач
spec-first set hooks --product billing --trigger after-close-all \
--add "pnpm nx build billing"
spec-first set hooks --product billing --trigger after-close-all \
--add "echo Закрыто АС: \$SPEC_FIRST_CLOSED_ACS"
spec-first close all --product billing
# ...
# [hook] > pnpm nx build billing
# [hook] ✔ OK
# [hook] > echo Закрыто АС: SPEC-001:AC-001,SPEC-001:AC-002
# [hook] ✔ OKУправление хуками через CLI
# Добавить команду (идемпотентно — повторный add игнорируется)
spec-first set hooks --product <id> --trigger after-close-ac --add "pnpm nx test myapp"
# Удалить конкретную команду (по точному совпадению строки)
spec-first set hooks --product <id> --trigger after-close-ac --remove "pnpm nx test myapp"
# Очистить весь триггер
spec-first set hooks --product <id> --trigger after-close-all --clear
# Посмотреть текущее состояние хуков
spec-first get config --product <id>Резолвинг runner'ов (по возрастанию приоритета)
- bundled (внутри пакета
@zurab/spec-first/runners/) - globalRunners (root config)
- extendRunners (root config — пользовательские)
- product.runners (per-product config — локальные для продукта)
При совпадении prefix выигрывает источник с большим приоритетом.
CLI команды
spec-first без аргументов печатает help. Все команды поддерживают -c, --config <path> для явного root config.
Чтение
spec-first get products список всех продуктов
spec-first get product <id> карточка продукта (resolved config)
spec-first get specs --product <id> список спек продукта
spec-first get next-task [--product <id>] первая незакрытая FR/AC + контекст
spec-first get fn --spec <id> FR указанной спеки
spec-first get list <us|fr|ac|spec> с фильтрами --product/--spec/--status
spec-first get replaced-by <id> миграционная цепочка для deprecated
spec-first get constitution <product> печать constitution.md
spec-first get config [--product <id>] resolved config (с merged runners)
spec-first get template <product> --kind passport|spec
spec-first get spec-batch-run <spec-id> SPEC-016: batchRun из frontmatter (--json → {specId,command})Создание / мутации (CLI-only invariant — markdown руками не редактируем)
spec-first init [--check] [--force] идемпотентная инициализация workspace
spec-first create product <id> [--title <t>] бутстрап продукта (вкл. constitution)
spec-first create spec <product> <slug> --title <t> новая спека из шаблона
spec-first add us <spec-id> --text "<...>" US-NNN с маркерами
spec-first add fn <us-id> --text "<...>" FR-NNN под US (FQN: SPEC-N:US-M)
spec-first add ac <fr-id> --text "<...>" \
--runner <prefix> --block <json> AC + runner-блок
spec-first add constitution <product> [--file|--text] создать конституцию (если её нет)
spec-first update <kind> <id> --text "<новый текст>" изменить текст элемента
spec-first set problem <spec-id> --context/--pain/--goal | --file
spec-first set constitution <product> --file|--text заменить (--file ИЛИ --text inline)
spec-first set template <product> --kind passport|spec --file <path>
spec-first set spec-batch-run <spec-id> --command "<bash>" SPEC-016: установить batchRun (с placeholder-поддержкой)
spec-first set spec-batch-run <spec-id> --clear SPEC-016: удалить batchRun из frontmatter
spec-first delete constitution <product> [--hard] soft (пустой файл) или hard (rm)add constitution отказывает (exit 1), если файл уже существует и не пуст — используйте set или delete + add. delete --soft (default) очищает содержимое, оставляя путь валидным; --hard физически удаляет файл.
Устаревание
spec-first deprecate <kind> <id> --replaced-by <ids> с заменой (CSV/FQN)
spec-first deprecate <kind> <id> --no-replacement --reason "" без замены
spec-first deprecate fr FR-001 --no-cascade не каскадироватьПри deprecate родителя потомки получают status=deprecated с deprecationReason="parent <id> deprecated" (если не указан явный replacedBy). FQN форма SPEC-NNN:ID поддерживается везде, где id может быть неоднозначным.
Иерархическое закрытие (status=done через runner-PASS)
spec-first close ac <id> AC: автоматически прогоняет runner; PASS ⇒ done
spec-first close fr <id> FR: требует чтобы все non-deprecated AC были done
spec-first close us <id> US: требует чтобы все non-deprecated FR были done
spec-first close spec <id> Spec: требует чтобы все non-deprecated US были done
spec-first close all [--product <id>] [--spec <id>] [--dry-run]
spec-first verify-closed [--product <id>] exit 1 если есть open/draftclose all идёт в порядке AC → FR → US → Spec, перезагружает индекс между фазами. На каждой ошибке: понятный reason (что и почему нельзя закрыть, список блокеров с id/status/text). update --status done тоже теперь делегируется в close — нет escape-hatch.
Re-validation (run — стрим-вывод без смены статусов)
spec-first run все AC всех продуктов
spec-first run --product <id> только продукт
spec-first run --spec <id> только спека
spec-first run ac <id> один AC (поддерживает FQN)
spec-first run --json машинный вывод
spec-first run --timeout <ms> таймаут одного runner (default 600000 = 10мин)
spec-first run --no-batch bypass batch-режима глобально для всех спек
spec-first run --no-batch=SPEC-NNN,SPEC-MMM bypass batch-режима точечно для указанных спекStream-формат: при старте каждой Spec/US/FR печатается префикс с заголовком, при завершении AC — ✓/✗ <id> [runner, durationMs] <tail-payload-or-error>. После каждого FR/US/Spec — агрегат Σ: pass/total + sum durationMs. В конце — Total: число AC, wall-time, sum AC time, и список FAIL-ов с reason от runner-а. Exit 0 если все PASS, 1 если хоть один FAIL. Файлы НЕ меняются.
Batch-режим спеки (SPEC-016)
Если у спеки задано поле batchRun в YAML-frontmatter, spec-first run прогоняет её одной bash-командой вместо итерации по отдельным AC. Вердикт определяется exit code (0 = PASS). Это ускоряет прогон для спек, у которых уже есть pnpm nx test или аналог.
Управление через CLI:
# Установить команду
spec-first set spec-batch-run SPEC-NNN --command "pnpm nx test ng-noor-checkbox"
# С placeholders (подставляются на лету перед spawn)
spec-first set spec-batch-run SPEC-NNN \
--command "pnpm nx test {product} --include='{specDir}/AC-*.spec.ts'"
# Прочитать текущее значение
spec-first get spec-batch-run SPEC-NNN
spec-first get spec-batch-run SPEC-NNN --json # → { specId, command }
# Удалить поле
spec-first set spec-batch-run SPEC-NNN --clearДопустимые placeholders:
| Placeholder | Значение |
|---|---|
| {product} | ID продукта |
| {specId} | ID спеки (SPEC-NNN) |
| {specUid} | UID спеки |
| {specDir} | абсолютный путь к директории файла спеки |
| {{ | литеральная { (escape) |
Поведение при FAIL: в stderr выводится WHERE (файл + команда) / WHY (первая строка stderr/stdout) / HOW (spec-first run --no-batch=SPEC-NNN).
Агрегат: batch-спека считается как 1 unit в статистике (не N AC); в JSON-выводе shape { batch: true, batchCommand, result: { status, exitCode, durationMs } }.
Bypass: флаг --no-batch (без значения = глобально, --no-batch=SPEC-NNN,SPEC-MMM = точечно) переключает batch-спеки обратно на поштучный прогон AC — удобно для отладки конкретного AC.
verify-spec-and-product-run — loop-oracle для CI и AI-агентов
Усиленный 2-step verifier (SPEC-008 + SPEC-012 + SPEC-014). Используется как финальный gate Phase D в любом implementation-loop runner-е (Ralph Wiggum Plugin, Cursor agent mode, Gemini CLI, plain bash while), а также в CI как single source of truth «всё ли реально работает».
# Через npx (рекомендованный способ для CI и AI-агентов):
npx -y -p @zurab/spec-first spec-first verify-spec-and-product-run <spec-fqn> --output stable
# Локально в монорепо:
node tools/spec-first/dist/cli/spec-first.js verify-spec-and-product-run <spec-fqn> --output stableЧто делает (2 step):
- Step 1 — статика: проверяет что указанная спека закрыта (все non-deprecated US/FR/AC в
status=done). - Step 2 — динамика: прогоняет ВСЕ AC-runner-ы продукта параллельно (3 спеки × 2 AC одновременно по умолчанию); даёт защиту от регрессий в других спеках при имплементации новой.
- Errors gate (если
errorTrackingEnabled=true): проверяет что вerrorsDirнетstatus=newзаписей.
4 режима вывода (--output <mode>):
| mode | PASS stdout | FAIL stdout | Назначение |
|---|---|---|---|
| promise (default) | <promise>SPEC-FIRST-VERIFIED:<product>:<spec>:<n1>:<n2></promise> | actionable отчёт с failing AC + sentinel <error>SPEC-FIRST-VERIFY-FAILED:...</error> | Человек-читатель + loop runner с regex-парсингом цифр |
| stable | ровно SPEC-FIRST-VERIFIED-OK\n | 4 строки: sentinel / WHERE+WHY / file / retry | Loop runner матчит байт-стабильную строку через [[ "$out" == "SPEC-FIRST-VERIFIED-OK" ]] |
| quiet | пусто | пусто (одна stderr-строка) | Exit-code-only сигнал для скриптов в pipe |
| stream | live-стрим всех AC + финальный promise | live-стрим + полный список failing | Дебаг / verbose |
WHERE / WHY / HOW (FR-019) — все режимы (кроме quiet) выводят при FAIL:
- WHERE — FQN (
SPEC-014:AC-035) + относительный путь к файлу спеки. - WHY — первая значимая строка причины (error → payload → stderr; ANSI/CR cleared, ≤200 символов).
- HOW — точная команда retry (
spec-first close ac <spec>:<ac>).
Параллелизм (SPEC-008):
- 2 семафора:
maxSpecStreams(default 3) ×maxAcStreams(default 2) — на 6 ACs одновременно. - CLI флаги:
--max-spec-streams=N,--max-ac-streams=N,--max-for-every=N(single value перебивает оба). - Per-workspace конфиг
verifyRunConcurrencyв.spec-first.json(root) или.spec-first.product.json(product) — наследуется отrunConcurrencyесли не задан. - Speedup измеренный: 5.4× на 262 AC (sum CPU 725s → wall 105s).
Output-config: verifyOutput: "promise" | "stable" | "quiet" | "stream" в root или product config — задаёт default, CLI перебивает.
Таймаут: --timeout <ms> (default 600000 = 10 минут) — на каждый runner отдельно.
Errors-gate isolation (SPEC-014 US-010): child-runner-ы по умолчанию НЕ captureят errors через env SPEC_FIRST_NO_AUTO_CAPTURE=1 (выставляется автоматически). Это предотвращает засорение errorsDir от deliberate-fail-тестов внутри runner-блоков. Чтобы получить child-capture для дебага: SPEC_FIRST_NO_AUTO_CAPTURE=0 spec-first verify-spec-and-product-run ... — explicit user-override побеждает default.
Exit-codes: 0 = PASS обоих step и errors-gate; 1 = FAIL Step 1 / Step 2 / errors-gate.
Best practices:
# CI: проверка что фича готова к merge — простой grep на sentinel.
if npx -y -p @zurab/spec-first spec-first verify-spec-and-product-run \
<spec-fqn> --output stable | grep -Fxq "SPEC-FIRST-VERIFIED-OK"; then
echo "✓ ready to merge"
else
echo "✗ verify failed — see retry hint in output"
exit 1
fi
# Loop runner (Ralph/Cursor/Gemini): match completion-promise.
spec-first verify-spec-and-product-run <spec-fqn>
# → ловить regex `<promise>SPEC-FIRST-VERIFIED:.*</promise>` из stdout
# → или `<error>SPEC-FIRST-VERIFY-FAILED:...:step([12]):.*</error>` для FAIL
# Pipe-friendly (только exit-code):
spec-first verify-spec-and-product-run <spec-fqn> --output quiet || \
echo "fail; check stderr for first failing AC"
# Для дебага долгого прогона (262 AC ≈ 2 минуты):
spec-first verify-spec-and-product-run <spec-fqn> --output streamНе путать с:
run— без status-checks, без errors-gate, без promise-sentinel; просто прогоняет AC ради вывода.verify-spec-implemented— только Step 1 (статика). Не запускает runner-ы.verify-closed— workspace-wide статика без runner-прогона.
Полный список параметров и FR — см. SPEC-008 (docs/spec-first-products/spec-first/docs/specs/1-spec/8-parallel-run.spec-first.ai.md), SPEC-012 (12-implement-playbook.spec-first.ai.md), SPEC-014 (14-errors.spec-first.ai.md).
Анализ
spec-first validate [target] [--strict] target = path к файлу либо productId
spec-first check-to-implement [--product <id>] [--spec <id>]validate проверяет: парные маркеры, уникальность id, наличие parent (FR→US, AC→FR), резолвинг replacedBy, согласованность checkbox/status, frontmatter. --strict дополнительно требует replacedBy или deprecationReason у любого deprecated элемента.
check-to-implement (pre-implementation gate): каждый non-deprecated Spec имеет ≥1 US, US ≥1 FR, FR ≥1 AC, AC ссылается на зарегистрированный runner и имеет валидный JSON-блок.
Сборка паспорта
spec-first build [--product <id>] [--no-preprocessor]Программно строит Config для @zurab/mdmanager#runPipeline. Bundled runners резолвятся по абсолютному пути из <dist>/runners/. Deprecated AC пропускаются (помечаются ⚠ deprecated, runner не запускается).
Канонический формат spec-файла
Каждая спека — markdown с YAML-frontmatter и HTML-маркерами. Маркеры невидимы при рендере, но дают однозначный машинный парсер.
---
specId: SPEC-001
product: billing
title: 'Чекаут'
status: draft
---
## Problem
<!-- spec-first:problem-start -->
**Context:** ...
**Pain:** ...
**Goal:** ...
<!-- spec-first:problem-end -->
## User Stories
<!-- spec-first:us-start id=US-001 status=open -->
- [ ] **US-001** — Как покупатель, я хочу оплатить заказ
<!-- spec-first:us-end -->
## Functional Requirements
<!-- spec-first:fr-start id=FR-001 us=US-001 status=open -->
- [ ] **FR-001** — Кнопка 'Оплатить' открывает виджет провайдера
<!-- spec-first:fr-end -->
## Acceptance Criteria
<!-- spec-first:ac-start id=AC-001 fr=FR-001 status=open runner=bashrunner -->
- [ ] **AC-001** — Клик по кнопке открывает iframe
```bashrunner
{ "fr": "AC-001", "command": "true", "expect": "pass" }
```
<!-- spec-first:ac-end -->Контракт маркера
<!-- spec-first:<kind>-(start|end) key=value... -->
| Атрибут | Где | Значения |
|---|---|---|
| id | spec, us, fr, ac | SPEC-001, US-001, FR-001, AC-001 |
| status | все | draft | open | done | deprecated |
| us | fr | id родительского US |
| fr | ac | id родительского FR |
| runner | ac | prefix runner-блока |
| replacedBy | при status=deprecated | id или CSV; FQN SPEC-N:ID для cross-spec |
| deprecationReason | при --no-replacement | URL-encoded строка |
Runner contract
Runner — исполняемый файл (node/bash/python), читающий JSON со stdin и пишущий JSON в stdout.
Stdin (передаёт mdmanager):
{
"blockBody": "<содержимое fenced-блока>",
"blockLanguage": "<prefix>",
"sourcePath": "<абсолютный путь к spec.md>",
"outputPath": "<куда пишется паспорт>",
"frontmatter": { },
"contextDoc": { "name": "...", "relativePath": "..." }
}Stdout:
{ "ok": true, "mode": "replace", "payload": "> ✅ AC-001 PASS\n" }или при ошибке:
{ "ok": false, "error": { "code": "BAD_BLOCK_JSON", "message": "..." } }Bundled prefix'ы (резолвятся по __dirname после build):
bashrunner—bash-runner.shjestrunner—jest-runner.jsvitestrunner—vitest-runner.mjscypressrunner—cypress-ct-runner.jspytestrunner—pytest-runner.pyplaywrightrunner—playwright-runner.js
Самотест runner'а: переменная MDMANAGER_SELF_TEST=1 мокает PASS без реального запуска.
Eat own dogfood
@zurab/spec-first сам построен по своим правилам: продукт spec-first живёт в docs/product-passports/products/spec-first/, его 3 спеки покрывают (1) маркеры, (2) discovery, (3) интеграцию с mdmanager. Сборка self-passport'а:
node tools/spec-first/dist/cli/spec-first.js build --product spec-firstPublic API
Пакет также экспортирует все команды и core-модули как программный API:
import {
loadIndex,
parseSpecText,
insertBlockInText,
buildPassport,
initCommand,
createProductCommand,
createSpecCommand,
addUsCommand,
validateCommand,
} from '@zurab/spec-first';Разработка
pnpm nx build tools-spec-first # сборка через @nx/js:tsc
pnpm nx test tools-spec-first # jest unit + integration
pnpm nx run tools-spec-first:eslint:lint # eslintВсе тесты используют tmp-dir и реальную FS, без сети, без npm i -g.
Goal-mode (SPEC-017, 0.14.0)
Зачем. Spec-flow требует ясной формулировки фичи до старта. Когда фича ещё не определена — есть только гипотеза «X улучшит Y» — нужен другой контейнер: персистентная цель с критериями достижения, которая собирает выводы и формирует черновики будущих спек по ходу проверки.
Жизненный цикл (3-state):
[create] [start] [mark-implemented]
draft ── наполнение ──→ new ── checklist run ──→ implemented
│ (principles, │ (все items=passed) │
│ checklist, deps, │ │
│ ARDs, tasks) └─ goal task promote ─→ create spec
└─ goal delete --confirm (deferred → реальная)
(только draft)goal resume <id> работает в любом статусе и не меняет его — печатает zero-context инъекцию: цель + актуальный контент привязанных ARDs (через прямой вызов ard get) + содержимое dependent-files (или missing если файл отсутствует) + open deferred-tasks одним вызовом.
Хранилище. Поле goalsDir в root config (default docs/goals). Файлы — <8-hex>.<slug>.goal.ai.md, формат frontmatter + markdown body. Frontmatter содержит id, title, description, hypothesis, success-criteria, status, product, массивы principles[], checklist[] (с runner-block + status), dependent-files[] (when: always|first-resume|on-implemented), related-ards[] (с snapshot-at), deferred-tasks[] (с kind: spec|extension|note и target-product), а также таймстемпы createdAt, startedAt, implementedAt.
CLI surface (~25 команд):
# Lifecycle
spec-first goal create --title T --description D --hypothesis H --success-criteria C [--product P]
spec-first goal start <id> # draft → new (требует ≥1 principle и ≥1 checklist)
spec-first goal mark-implemented <id> # new → implemented (все checklist passed)
spec-first goal delete <id> --confirm # hard-delete (только draft)
# Базовые
spec-first goal list [--status] [--product] [--description] [--json]
spec-first goal get <id> [--json]
spec-first goal count [--status] [--product] [--json]
spec-first goal resume <id> # zero-context инъекция
# Principles (мини-конституция конкретной цели)
spec-first goal principles add <id> --title T --text X
spec-first goal principles list <id> [--json]
spec-first goal principles remove <id> --pid <slug>
# Checklist (только bashrunner для MVP)
spec-first goal checklist add <id> --text T --runner-prefix bashrunner --runner-block '{"command":"...","expect":"pass"}'
spec-first goal checklist list <id> [--json]
spec-first goal checklist run <id> [--item <iid>] # без --item: запустит все, агрегирует PASS/FAIL
spec-first goal checklist set <id> --item <iid> --text T
spec-first goal checklist remove <id> --item <iid>
# Dependent files (context injection при resume)
spec-first goal deps add <id> --path <rel> --when always|first-resume|on-implemented --why <reason>
spec-first goal deps list <id> [--json]
spec-first goal deps remove <id> --path <rel>
# Related ARDs (snapshot + sync)
spec-first goal ard add <id> --ard ARD-NNN
spec-first goal ard list <id> [--json]
spec-first goal ard sync <id> # обновить snapshot-at до now
spec-first goal ard remove <id> --ard ARD-NNN
# Deferred tasks (черновики будущих спек)
spec-first goal task add <id> --title T --description D [--kind spec|extension|note] [--target-product P]
spec-first goal task list <id> [--status] [--json]
spec-first goal task promote <goalId>:<taskId> [--apply] # без --apply: dry-run; с --apply для kind=spec → create spec
spec-first goal task discard <id> --task <tid> # soft: status → discarded
spec-first goal task remove <id> --task <tid> # hard: убрать из массиваWorkflow (типовой):
- Создаёшь цель с title + description + hypothesis + success-criteria.
- Подгружаешь актуальные
ard list --status approvedи предлагаешь пользователю прикрепить релевантные черезgoal ard add. - Формируешь principles (≥1) — инварианты конкретной гипотезы.
- Формируешь checklist — каждый пункт = bashrunner-блок, один Then-критерий.
- Добавляешь dependent-files (constitution, релевантные специ, конфиги).
goal start <id>(draft → new).- По мере проверки гипотезы —
goal task addдля всего, что может стать спекой. - Когда все checklist items = passed →
goal mark-implemented <id>. - Пользователь смотрит
goal task list <id>, отбраковывает (task discard), promote'ит подтверждённые:goal task promote <id>:<task-id> --applyдляkind=specсоздаёт реальную спеку.
Skill mz-spec-first. В версии 0.14.0 SKILL.md переработан в dispatcher (~87 строк): intent-таблица роутит к одному из трёх reference: goal-workflow.md (этот режим), spec-workflow.md (классический 4-фазный flow), cli-cheatsheet.md (топ-команды по группам). Полный workflow goal-mode — в skills/mz-spec-first/references/goal-workflow.md.
Архитектурные принципы
- CLI-only invariant — markdown spec-файлы НИКОГДА не редактируются руками. Любые изменения — через CLI; каждая mutate-команда атомарна (temp + rename).
- Two-level config — root для discovery + per-product для деталей.
- Bundled runners — пакет самодостаточен, не требует копирования внешних скриптов.
- CLI-flat программный API — каждая команда экспортирует функцию (
initCommand,addUsCommand, ...) для встраивания в другие инструменты. - Constitution-sync invariant (с 0.14.0) — конституция продукта (
constitution.md) всегда отражает актуальный набор принципов и режимов работы. После любых изменений в спеках, режимах (например добавление goal-mode), CLI-инвариантах или жизненных циклах — конституция обновляется тем же изменением черезset constitution <product> --file|--text. Расхождение конституции и поведения CLI считается багом конституции. - Release artifacts sync invariant (с 0.14.0) —
CHANGELOG.mdиREADME.mdпакета обновляются как часть того же изменения, которое добавляет/меняет функциональность. CHANGELOG ведётся по Keep a Changelog с секциями Added/Changed/Fixed/Tests; README — публичная витрина пакета на npm и описывает любые новые поля конфига, CLI-команды и режимы. Релиз с обновлением кода без CHANGELOG/README — неполный.
См. также конституцию продукта в docs/product-passports/products/spec-first/constitution.md и план ai/plans/spec-first-sleepy-fountain.md.
