Lint the spec-to-test linkage #41

Open
opened 2026-08-10 13:10:10 +00:00 by claude · 0 comments
Collaborator

Summary

Скрипт-линтер, который сверяет три множества: **TEST** TC-… в спеках, записи
### TC-… в tests.md и id в docstring реальных тестов. Расхождение — ошибка.

Spec

none

Depends on

  • fork-a-spec-tdd-workflow-schema — линтер сторожит формат, который вводит схема

Motivation

OpenSpec не проверяет внутренность сценария вообще. Это установлено пробой
на одноразовом репозитории, а не предположено.

Сценарий вида

#### Scenario: No when no then
- **TEST** TC-BOGUS-not-an-id
- total gibberish with no structure at all

проходит openspec validate --strict как valid, exit 0. Парсер хранит тело
сценария непрозрачным rawText (видно в openspec show --json), а требует
только заголовок #### Scenario: и непустое тело.

Что из этого следует: openspec никогда не поймает ни опечатку в TC id, ни
забытый **TEST**, ни два сценария с одним и тем же кейсом, ни сценарий вовсе
без WHEN/THEN. Весь контроль формата держится на этом скрипте — больше держать
его некому.

Та же проба показала, что носитель выбран правильно, и это единственная
причина, по которой связка вообще возможна:

Проверка Результат
openspec validate <change> с буллетом **TEST** valid
то же с --strict valid
archive ADDED-дельты в openspec/specs/<cap>/spec.md буллеты байт в байт
archive MODIFIED-дельты, включая новый сценарий с новым TC буллеты сохранены
openspec validate --specs --strict на слитой спеке passed

Ещё деталь, которая определяет реализацию: openspec show --json теряет
имена сценариев
— в JSON остаётся только rawText. Значит линтер парсит
markdown сам, вывод CLI как источник не годится.

Acceptance criteria

  • есть скрипт-линтер, запускается без аргументов из корня репозитория и сам
    находит корень по .git/AGENTS.md, а не по cwd
  • каждый #### Scenario: содержит **WHEN**, **THEN** и ровно один
    **TEST**; нарушение печатает path/file.md:line
  • TC id матчит TC-[A-Z]+-\d{3}, иначе ошибка
  • TC уникален глобально — сверка идёт и по openspec/changes/*/specs/, и по
    openspec/specs/, чтобы новый change не переиспользовал номер, уже
    уехавший в архив
  • каждый TC из спек change присутствует в tests.md этого change
  • каждый TC из tests.md присутствует в docstring хотя бы одного теста
    под tests/
  • TC, объявленный в tests.md и не встречающийся ни в одной спеке, — ошибка
  • exit 1 при любой ошибке, exit 0 на согласованном дереве, каждая ошибка —
    одна строка с путём
  • только stdlib, никаких зависимостей, subprocess не импортируется

Constraints

  • Не входит в объём: автоматическое выделение TC id. Номер назначает автор
    tests.md, линтер только проверяет уникальность.
  • Не входит в объём: запуск самих тестов. Линтер проверяет связи, зелёные они
    или нет — дело python3 -m unittest.
  • Не входит в объём: правки в openspec validate. Пакет не наш.
<!-- tea:id lint-the-spec-to-test-linkage --> ## Summary Скрипт-линтер, который сверяет три множества: `**TEST** TC-…` в спеках, записи `### TC-…` в `tests.md` и id в docstring реальных тестов. Расхождение — ошибка. ## Spec none ## Depends on - fork-a-spec-tdd-workflow-schema — линтер сторожит формат, который вводит схема ## Motivation **OpenSpec не проверяет внутренность сценария вообще.** Это установлено пробой на одноразовом репозитории, а не предположено. Сценарий вида ``` #### Scenario: No when no then - **TEST** TC-BOGUS-not-an-id - total gibberish with no structure at all ``` проходит `openspec validate --strict` как `valid`, exit 0. Парсер хранит тело сценария непрозрачным `rawText` (видно в `openspec show --json`), а требует только заголовок `#### Scenario:` и непустое тело. Что из этого следует: openspec никогда не поймает ни опечатку в TC id, ни забытый `**TEST**`, ни два сценария с одним и тем же кейсом, ни сценарий вовсе без WHEN/THEN. Весь контроль формата держится на этом скрипте — больше держать его некому. Та же проба показала, что носитель выбран правильно, и это единственная причина, по которой связка вообще возможна: | Проверка | Результат | |---|---| | `openspec validate <change>` с буллетом `**TEST**` | valid | | то же с `--strict` | valid | | archive ADDED-дельты в `openspec/specs/<cap>/spec.md` | буллеты байт в байт | | archive MODIFIED-дельты, включая новый сценарий с новым TC | буллеты сохранены | | `openspec validate --specs --strict` на слитой спеке | passed | Ещё деталь, которая определяет реализацию: `openspec show --json` **теряет имена сценариев** — в JSON остаётся только `rawText`. Значит линтер парсит markdown сам, вывод CLI как источник не годится. ## Acceptance criteria - [ ] есть скрипт-линтер, запускается без аргументов из корня репозитория и сам находит корень по `.git`/`AGENTS.md`, а не по cwd - [ ] каждый `#### Scenario:` содержит `**WHEN**`, `**THEN**` и ровно один `**TEST**`; нарушение печатает `path/file.md:line` - [ ] TC id матчит `TC-[A-Z]+-\d{3}`, иначе ошибка - [ ] TC уникален глобально — сверка идёт и по `openspec/changes/*/specs/`, и по `openspec/specs/`, чтобы новый change не переиспользовал номер, уже уехавший в архив - [ ] каждый TC из спек change присутствует в `tests.md` этого change - [ ] каждый TC из `tests.md` присутствует в docstring хотя бы одного теста под `tests/` - [ ] TC, объявленный в `tests.md` и не встречающийся ни в одной спеке, — ошибка - [ ] exit 1 при любой ошибке, exit 0 на согласованном дереве, каждая ошибка — одна строка с путём - [ ] только stdlib, никаких зависимостей, `subprocess` не импортируется ## Constraints - Не входит в объём: автоматическое выделение TC id. Номер назначает автор `tests.md`, линтер только проверяет уникальность. - Не входит в объём: запуск самих тестов. Линтер проверяет связи, зелёные они или нет — дело `python3 -m unittest`. - Не входит в объём: правки в `openspec validate`. Пакет не наш.
claude added this to the openspec integration milestone 2026-08-10 13:10:10 +00:00
claude added the
type
task
comp/openspec
labels 2026-08-10 13:10:10 +00:00
claude added a new dependency 2026-08-10 13:10:10 +00:00
claude added a new dependency 2026-08-10 13:10:11 +00:00
claude added a new dependency 2026-08-10 13:10:12 +00:00
Sign in to join this conversation.