границы плагинов: путь в чужое дерево, безымянные стыки, звонящий у вычитки
Правило границы моё, копий восемь — и нарушал его я же. - путь в дерево чужого плагина снят из пяти мест; маркер копии, уезжающий в проект скелетом, оставлен, но сказано, что сама пара маркеров не едет - короткое имя чужого скилла в четырёх местах стало полным - стык «урожай ревью → задачи» не был назван ни с одной стороны, хотя механика написана с обеих; теперь назван, с веткой «плагина нет» - resolve звал av-dev-git:commit без строки доклада и пересказывал формат коммита, нарушая собственное «ссылайся, не пересказывай» - doc-wording обещал момент вызова, которого не исполнял никто. Правило: звонящий — тот, кто только что писал текст. Вызов появился шагом в docs, init, adopt и upgrade; healthcheck по-прежнему его не зовёт - openspec.py искал SHALL по всему файлу, а образец даёт его в context — проверка молчала ровно в том случае, ради которого написана - фаза 2 review-rubric была недостижима; проход стал судить задуманное, а не код, и это сходится с тем, что о нём говорит конвейер - rules.tasks в образце конфига, ветка «записи задачи нет» у review-scope, возвраты на чекпоинт в схеме resolve, старшинство правила дельта-спек Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,35 @@ def rules_keys(live: str) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def rules_block(live: str, name: str) -> str:
|
||||
"""Строки правил, адресованных одному артефакту.
|
||||
|
||||
Обход тот же, что у `rules_keys`, и по той же причине: искать по всему файлу
|
||||
нельзя. Литеральный скаляр `context` называет `SHALL` уже в образце, поэтому
|
||||
проверка «правила называют SHALL» грепом по файлу проходила при **пустом**
|
||||
`rules.specs` — то есть молчала ровно в том случае, ради которого написана.
|
||||
"""
|
||||
out: list[str] = []
|
||||
in_rules = False
|
||||
in_name = False
|
||||
for line in live.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
if not line[0].isspace():
|
||||
in_rules = line.startswith("rules:")
|
||||
in_name = False
|
||||
continue
|
||||
if not in_rules:
|
||||
continue
|
||||
m = re.fullmatch(r" ([A-Za-z_-]+):\s*", line)
|
||||
if m:
|
||||
in_name = m.group(1) == name
|
||||
continue
|
||||
if in_name:
|
||||
out.append(line)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def check_form(root: Path, rep: Report) -> None:
|
||||
"""Настройка заведена и не осталась примером из коробки."""
|
||||
os_dir = root / "openspec"
|
||||
@@ -197,12 +226,12 @@ def check_form(root: Path, rep: Report) -> None:
|
||||
if pointer not in live:
|
||||
rep.error(f"openspec/config.yaml не называет {pointer} — {why}")
|
||||
|
||||
if "rules" not in keys or "specs:" not in live:
|
||||
if "rules" not in keys or "specs" not in rules_keys(live):
|
||||
rep.error(
|
||||
"в openspec/config.yaml нет rules.specs — придирки валидатора "
|
||||
"нигде не записаны, и каждое предложение узнаёт их отказом"
|
||||
)
|
||||
elif "SHALL" not in live:
|
||||
elif "SHALL" not in rules_block(live, "specs"):
|
||||
rep.error(
|
||||
"rules.specs в openspec/config.yaml не называет SHALL — "
|
||||
"требование без этого литерала валидатор отвергает, а правило "
|
||||
|
||||
Reference in New Issue
Block a user