старшинство диаграмм объявлено, рендер проверяется скриптом
Диаграмма и проза вокруг неё описывают один факт — это второй дом, и разойтись они могут молча: то самое, против чего написан copies.py. Механической сверки здесь нет, дословного соответствия между текстом и графом не существует, поэтому работает объявление. В review-pipeline старший граф — он и есть алгоритм планировщика, проза объясняет рёбра; в остальных местах старшая проза, диаграмма там сводка; в calibration.md старшая таблица вердиктов, схема добавляет к ней только счётчик. Объявление стоит у каждой диаграммы строкой в месте, а не общим правилом в README: скилл читают целиком, README — нет. В task-batch добавлена оговорка про соседний скилл — два вызова с разным старшинством рядом это место, где легко ошибиться. scripts/diagrams.py вынимает все mermaid-блоки и рендерит каждый через mmdc или npx @mermaid-js/mermaid-cli. Коды выхода — общий словарь; нет рендерера — код 3, а не молчаливый успех. Chromium с --no-sandbox: без флага падает на «No usable sandbox», причина в докстроке. Проверены обе ветки: 11 диаграмм в 9 файлах зелено, сломанный блок даёт точное место с текстом ошибки парсера и код 1. README: раздел «Проверка диаграмм» рядом с проверкой копий — что ловит, чего не ловит и почему не в гейте. DECISIONS 63 и 64. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1065,3 +1065,18 @@ HTML-комментарии, невидимые в отрендеренном ma
|
|||||||
62. **Проход, держащий машину, знает об этом из своего charter'а.** `adversary` и
|
62. **Проход, держащий машину, знает об этом из своего charter'а.** `adversary` и
|
||||||
`ops` получили по абзацу: цепочка гарантирует им чистое железо, значит их
|
`ops` получили по абзацу: цепочка гарантирует им чистое железо, значит их
|
||||||
число — оракул, и шум в нём объясняется замером, а не соседом.
|
число — оракул, и шум в нём объясняется замером, а не соседом.
|
||||||
|
63. **У каждой диаграммы объявлено старшинство — это цена второго дома.** Схема
|
||||||
|
и проза вокруг неё описывают один факт, и разойтись они могут молча: то
|
||||||
|
самое, против чего написан `copies.py`. Механической сверки здесь нет —
|
||||||
|
дословного соответствия между текстом и графом не существует, — поэтому
|
||||||
|
работает объявление: **в `review-pipeline` старший граф** (он и есть алгоритм
|
||||||
|
планировщика, проза объясняет рёбра), **в остальных местах старшая проза**
|
||||||
|
(диаграмма там сводка). Для агента это не философия: без объявления он идёт
|
||||||
|
за тем, что конкретнее, то есть чаще за схемой.
|
||||||
|
64. **Рендер диаграмм проверяется скриптом, а не памятью автора.**
|
||||||
|
`scripts/diagrams.py` вынимает все блоки `mermaid` и гонит их через
|
||||||
|
`mmdc` или `npx @mermaid-js/mermaid-cli`; коды выхода — общий словарь, нет
|
||||||
|
рендерера — код 3, а не молчаливый успех. Причина та же, что у остальных
|
||||||
|
проверок репозитория: **ошибка в блоке не видна при чтении** — текст
|
||||||
|
правдоподобен, дифф разумен, падает только рендер. Расхождение с прозой
|
||||||
|
скрипт не ловит и не притворяется, что ловит: это работа правила 63.
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ openspec/
|
|||||||
<plugin>/skills/<skill>/references/ что читается по ссылке из скилла
|
<plugin>/skills/<skill>/references/ что читается по ссылке из скилла
|
||||||
<plugin>/skills/<skill>/scripts/ tasks.py, docs.py
|
<plugin>/skills/<skill>/scripts/ tasks.py, docs.py
|
||||||
<plugin>/agents/ charter'ы сабагентов
|
<plugin>/agents/ charter'ы сабагентов
|
||||||
|
scripts/ проверки самого репозитория: копии, диаграммы
|
||||||
pyproject.toml линтеры скриптов, только для этого репозитория
|
pyproject.toml линтеры скриптов, только для этого репозитория
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -197,3 +198,23 @@ uv run python scripts/copies.py # 0 сошлось, 1 расхождение
|
|||||||
Скрипт ловит четыре вещи: копия разошлась с домом (с диффом), копия указывает не
|
Скрипт ловит четыре вещи: копия разошлась с домом (с диффом), копия указывает не
|
||||||
на тот файл, дом остался без копий, разметка сломана. Чего он **не** ловит —
|
на тот файл, дом остался без копий, разметка сломана. Чего он **не** ловит —
|
||||||
копию, которую забыли пометить: помечать — по-прежнему решение человека.
|
копию, которую забыли пометить: помечать — по-прежнему решение человека.
|
||||||
|
|
||||||
|
## Проверка диаграмм
|
||||||
|
|
||||||
|
Диаграммы `mermaid` живут исходником в markdown — картинок в репозитории нет.
|
||||||
|
Синтаксическая ошибка в блоке **не видна при чтении**: текст выглядит
|
||||||
|
правдоподобно, диff показывает разумную строку, а рендер падает.
|
||||||
|
|
||||||
|
```
|
||||||
|
uv run python scripts/diagrams.py # 0 рендерятся, 1 нет, 3 нет mermaid-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
Рендерит `mmdc` с PATH или `npx --yes @mermaid-js/mermaid-cli`; ни того ни
|
||||||
|
другого нет — код 3, а не молчаливый успех. Прогон занимает секунды на блок,
|
||||||
|
поэтому он не в гейте, а в руках того, кто правит диаграмму.
|
||||||
|
|
||||||
|
Чего проверка **не** ловит — расхождение диаграммы с прозой вокруг неё. Дословного
|
||||||
|
соответствия между текстом и графом нет, сличать нечего, и держится это
|
||||||
|
правилом старшинства, записанным рядом с каждой диаграммой: **в конвейере ревью
|
||||||
|
старший граф** (он и есть алгоритм планировщика, проза его объясняет), **в
|
||||||
|
остальных местах старшая проза** (диаграмма там сводка).
|
||||||
|
|||||||
@@ -226,6 +226,10 @@ flowchart TD
|
|||||||
`code` и первый из меряющей пары, — а второй меряющий идёт следом за первым. В
|
`code` и первый из меряющей пары, — а второй меряющий идёт следом за первым. В
|
||||||
`quick` — `specs` и `code` разом, и сразу триаж.
|
`quick` — `specs` и `code` разом, и сразу триаж.
|
||||||
|
|
||||||
|
**Схема здесь старше прозы.** Она не иллюстрация к тексту, а сам алгоритм
|
||||||
|
планировщика; проза ниже объясняет рёбра и называет их цену. Разошлись — прав
|
||||||
|
граф, а расхождение чинится правкой текста.
|
||||||
|
|
||||||
**Ребро значит «A закончил раньше, чем B стартовал», и ничего больше.** В обычном
|
**Ребро значит «A закончил раньше, чем B стартовал», и ничего больше.** В обычном
|
||||||
графе задач ребро тянет за собой данные — здесь нет, и это не деталь реализации.
|
графе задач ребро тянет за собой данные — здесь нет, и это не деталь реализации.
|
||||||
Проход **не видит** находок других проходов, в каком бы порядке их ни запустили.
|
Проход **не видит** находок других проходов, в каком бы порядке их ни запустили.
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ stateDiagram-v2
|
|||||||
r2 --> dead: снова не ловит — это театр
|
r2 --> dead: снова не ловит — это театр
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Схема — **сводка** к таблице вердиктов выше: она добавляет только счётчик, и при
|
||||||
|
расхождении прав таблица.
|
||||||
|
|
||||||
**`retune` не более двух раз подряд.** Проход, не находящий дефект своего класса
|
**`retune` не более двух раз подряд.** Проход, не находящий дефект своего класса
|
||||||
в 2 из 3 прогонов после двух правок промпта, — это театр. Удалять, а не
|
в 2 из 3 прогонов после двух правок промпта, — это театр. Удалять, а не
|
||||||
бесконечно править формулировки: каждая итерация правки промпта стоит дороже,
|
бесконечно править формулировки: каждая итерация правки промпта стоит дороже,
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ flowchart TD
|
|||||||
чаще, чем ловит, снимается в прозу. Ребро `rule → clean` **обязательное**: без
|
чаще, чем ловит, снимается в прозу. Ребро `rule → clean` **обязательное**: без
|
||||||
него первые два шага не окупаются, а именно его и пропускают.
|
него первые два шага не окупаются, а именно его и пропускают.
|
||||||
|
|
||||||
|
Схема — **сводка**: условия каждого шага в его разделе, и при расхождении прав
|
||||||
|
текст.
|
||||||
|
|
||||||
## Шаг 1. Находка → конвенция
|
## Шаг 1. Находка → конвенция
|
||||||
|
|
||||||
Условия: находка **принята** при ревью (не отвергнута, не понижена в гипотезу) и
|
Условия: находка **принята** при ревью (не отвергнута, не понижена в гипотезу) и
|
||||||
|
|||||||
@@ -176,6 +176,10 @@ flowchart TD
|
|||||||
линеаризации законны); **параллельно** — волна 1 `A` одна (замеряющая), волна 2
|
линеаризации законны); **параллельно** — волна 1 `A` одна (замеряющая), волна 2
|
||||||
`B` и `C`, волна 3 `D`.
|
`B` и `C`, волна 3 `D`.
|
||||||
|
|
||||||
|
Схемы в этом скилле — **пример и сводка**, правила ставит текст: при расхождении
|
||||||
|
прав он. (В `av-dev-pipeline:review-pipeline` наоборот — там граф прогона и есть
|
||||||
|
алгоритм, и старший он.)
|
||||||
|
|
||||||
Покажи план короткой репликой — режим, порядок или состав волн, какие задачи
|
Покажи план короткой репликой — режим, порядок или состав волн, какие задачи
|
||||||
признаны замеряющими и по какому триггеру, — и иди дальше.
|
признаны замеряющими и по какому триггеру, — и иди дальше.
|
||||||
|
|
||||||
|
|||||||
@@ -178,6 +178,9 @@ flowchart TD
|
|||||||
порядок «сперва коммит работы, потом коммит учёта» на схеме тоже ребро, и оно
|
порядок «сперва коммит работы, потом коммит учёта» на схеме тоже ребро, и оно
|
||||||
обязательное (шаг 11).
|
обязательное (шаг 11).
|
||||||
|
|
||||||
|
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при
|
||||||
|
расхождении прав текст.
|
||||||
|
|
||||||
### 1. Прочитать задачу
|
### 1. Прочитать задачу
|
||||||
|
|
||||||
Задача задана извне (slug, файл, ссылка, описание) — прочитай её и связанные
|
Задача задана извне (slug, файл, ссылка, описание) — прочитай её и связанные
|
||||||
|
|||||||
@@ -140,6 +140,9 @@ flowchart TD
|
|||||||
s4 --> sprint
|
s4 --> sprint
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Схема — **сводка**: процедура каждого шага в
|
||||||
|
[references/cadence.md](references/cadence.md), и при расхождении прав текст.
|
||||||
|
|
||||||
Процедура каждого шага, размер и отбор порции, храповик на залежавшихся, формат
|
Процедура каждого шага, размер и отбор порции, храповик на залежавшихся, формат
|
||||||
интерактива и доклад — [references/cadence.md](references/cadence.md).
|
интерактива и доклад — [references/cadence.md](references/cadence.md).
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ flowchart TD
|
|||||||
`sprint close`** (после команды автотег уже не поставится) и **блокер в обход
|
`sprint close`** (после команды автотег уже не поставится) и **блокер в обход
|
||||||
исходов** (спринт распускается, а не ждёт).
|
исходов** (спринт распускается, а не ждёт).
|
||||||
|
|
||||||
|
Схема — **сводка**: определение готовности и правила приёмки ниже, и при
|
||||||
|
расхождении прав текст.
|
||||||
|
|
||||||
**Урожай заводится при закрытии спринта, а не при закрытии задачи.** Это
|
**Урожай заводится при закрытии спринта, а не при закрытии задачи.** Это
|
||||||
обязанность закрывающего: пройти по спискам находок от исполнителей и завести
|
обязанность закрывающего: пройти по спискам находок от исполнителей и завести
|
||||||
недостающее интейком скилла `tasks` — с дедупликацией и картой человеку. Заводимое
|
недостающее интейком скилла `tasks` — с дедупликацией и картой человеку. Заводимое
|
||||||
|
|||||||
@@ -105,6 +105,9 @@ stateDiagram-v2
|
|||||||
нет намеренно — каждый переход это команда, и другого способа его совершить не
|
нет намеренно — каждый переход это команда, и другого способа его совершить не
|
||||||
существует.
|
существует.
|
||||||
|
|
||||||
|
Схема — **сводка**: условия и оговорки живут в тексте разделов, и при
|
||||||
|
расхождении прав текст.
|
||||||
|
|
||||||
## Цели
|
## Цели
|
||||||
|
|
||||||
**Цель — такой же файл в `items/`, тип `[goal]`**, перечисленный в `PLAN.md`:
|
**Цель — такой же файл в `items/`, тип `[goal]`**, перечисленный в `PLAN.md`:
|
||||||
|
|||||||
@@ -62,5 +62,6 @@ project-includes = [
|
|||||||
"av-dev-pm/skills/tasks/scripts/tasks.py",
|
"av-dev-pm/skills/tasks/scripts/tasks.py",
|
||||||
"av-dev-pm/skills/canon/scripts/docs.py",
|
"av-dev-pm/skills/canon/scripts/docs.py",
|
||||||
"scripts/copies.py",
|
"scripts/copies.py",
|
||||||
|
"scripts/diagrams.py",
|
||||||
]
|
]
|
||||||
python-version = "3.12"
|
python-version = "3.12"
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Проверка mermaid-диаграмм в markdown этого репозитория.
|
||||||
|
|
||||||
|
Диаграммы заведены там, где структура — граф или автомат: порядок проходов
|
||||||
|
ревью, жизненный цикл записи по индексам, исходы задачи в спринте, храповик
|
||||||
|
промоута, счётчик калибровки, граф вызовов между плагинами.
|
||||||
|
|
||||||
|
Проверка нужна по одной причине: **синтаксическая ошибка в блоке не видна при
|
||||||
|
чтении**. Текст диаграммы выглядит правдоподобно, `git diff` показывает разумную
|
||||||
|
строку, ревью её пропускает — а отрендерить не удаётся, и читатель видит вместо
|
||||||
|
схемы полотно исходника либо сообщение об ошибке. Первый раз это ловилось тем,
|
||||||
|
что автор не забыл прогнать рендер руками; на «не забыл» проверки не строятся.
|
||||||
|
|
||||||
|
Чего проверка **не** ловит: расхождение диаграммы с прозой вокруг неё. Это
|
||||||
|
второй дом для одного факта, и удержать его в синхроне может только правило
|
||||||
|
старшинства, записанное рядом с каждой диаграммой (в конвейере ревью старший —
|
||||||
|
граф, он и есть алгоритм; в остальных местах старшая — проза, диаграмма там
|
||||||
|
сводка). Механической сверки для этого нет: между текстом и графом нет
|
||||||
|
дословного соответствия, которое можно было бы сличить, — в отличие от копий
|
||||||
|
правил, где оно есть и где его сверяет copies.py.
|
||||||
|
|
||||||
|
Рендерит `mermaid-cli`: бинарь `mmdc`, если он на PATH, иначе
|
||||||
|
`npx --yes @mermaid-js/mermaid-cli`. Ни того ни другого нет — это код 3, а не
|
||||||
|
молчаливый успех: проверка, отчитавшаяся «диаграммы в порядке», ничего не
|
||||||
|
отрендерив, хуже отсутствующей.
|
||||||
|
|
||||||
|
Chromium запускается с `--no-sandbox`: на современных дистрибутивах
|
||||||
|
непривилегированные user namespaces выключены, и без флага рендер падает на
|
||||||
|
«No usable sandbox». Содержимое здесь своё и локальное, так что песочница ничего
|
||||||
|
не защищает — она только мешает запуску.
|
||||||
|
|
||||||
|
Коды выхода — тот же словарь, что у tasks.py, docs.py и copies.py:
|
||||||
|
0 все диаграммы рендерятся
|
||||||
|
1 диаграмма не рендерится
|
||||||
|
2 ошибка употребления: аргументы
|
||||||
|
3 окружение: не тот каталог, нет mermaid-cli
|
||||||
|
4 внутренний сбой
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||||
|
|
||||||
|
FENCE_OPEN = re.compile(r"^\s*```mermaid\s*$")
|
||||||
|
FENCE_CLOSE = re.compile(r"^\s*```\s*$")
|
||||||
|
|
||||||
|
# Каталоги, где markdown не наш: клоны, окружения, временное.
|
||||||
|
SKIP = {".git", ".venv", "node_modules", "tmp", ".ruff_cache"}
|
||||||
|
|
||||||
|
# Стек вызовов puppeteer/mermaid полезного не несёт — режем по первой его строке.
|
||||||
|
NOISE = re.compile(r"^(Parser\.|\s+at )")
|
||||||
|
# Строки хода работы: не ошибка, а прогресс рендера.
|
||||||
|
PROGRESS = re.compile(r"^(Generating|Found \d+ mermaid)")
|
||||||
|
|
||||||
|
|
||||||
|
class Block:
|
||||||
|
"""Один блок ```mermaid: где начался и что внутри."""
|
||||||
|
|
||||||
|
def __init__(self, path: Path, line: int, text: str, root: Path) -> None:
|
||||||
|
self.path = path
|
||||||
|
self.line = line
|
||||||
|
self.text = text
|
||||||
|
self.where = f"{path.relative_to(root).as_posix()}:{line}"
|
||||||
|
|
||||||
|
|
||||||
|
def collect(root: Path) -> list[Block]:
|
||||||
|
"""Все mermaid-блоки репозитория, в порядке обхода."""
|
||||||
|
found: list[Block] = []
|
||||||
|
for path in sorted(root.rglob("*.md")):
|
||||||
|
if any(part in SKIP for part in path.relative_to(root).parts):
|
||||||
|
continue
|
||||||
|
lines = path.read_text(encoding="utf-8").splitlines()
|
||||||
|
start: int | None = None
|
||||||
|
body: list[str] = []
|
||||||
|
for i, line in enumerate(lines, start=1):
|
||||||
|
if start is None:
|
||||||
|
if FENCE_OPEN.match(line):
|
||||||
|
start, body = i, []
|
||||||
|
continue
|
||||||
|
if FENCE_CLOSE.match(line):
|
||||||
|
found.append(Block(path, start, "\n".join(body) + "\n", root))
|
||||||
|
start = None
|
||||||
|
continue
|
||||||
|
body.append(line)
|
||||||
|
if start is not None:
|
||||||
|
found.append(Block(path, start, "\n".join(body) + "\n", root))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def renderer() -> list[str] | None:
|
||||||
|
"""Команда рендера или None, если mermaid-cli недоступен."""
|
||||||
|
mmdc = shutil.which("mmdc")
|
||||||
|
if mmdc:
|
||||||
|
return [mmdc]
|
||||||
|
if shutil.which("npx"):
|
||||||
|
return ["npx", "--yes", "@mermaid-js/mermaid-cli"]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def render(cmd: list[str], block: Block, workdir: Path, config: Path) -> str | None:
|
||||||
|
"""Отрендерить блок. None — получилось, иначе сообщение об ошибке."""
|
||||||
|
src = workdir / "d.mmd"
|
||||||
|
src.write_text(block.text, encoding="utf-8")
|
||||||
|
out = workdir / "d.svg"
|
||||||
|
done = subprocess.run(
|
||||||
|
[*cmd, "-p", str(config), "-i", str(src), "-o", str(out)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if done.returncode == 0 and out.exists():
|
||||||
|
out.unlink()
|
||||||
|
return None
|
||||||
|
noise = f"{done.stdout}\n{done.stderr}"
|
||||||
|
useful = []
|
||||||
|
for line in noise.splitlines():
|
||||||
|
if NOISE.match(line):
|
||||||
|
break
|
||||||
|
if line.strip() and not PROGRESS.match(line.strip()):
|
||||||
|
useful.append(line.strip())
|
||||||
|
return "\n ".join(useful[:8]) or f"код {done.returncode} без сообщения"
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(description="Проверка mermaid-диаграмм.")
|
||||||
|
ap.add_argument("--dir", default=".", help="корень репозитория")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
root = Path(args.dir).resolve()
|
||||||
|
if not (root / ".claude-plugin").is_dir():
|
||||||
|
print(f"окружение: {root} не похож на корень репозитория"
|
||||||
|
f" (нет .claude-plugin)", file=sys.stderr)
|
||||||
|
return ENV
|
||||||
|
|
||||||
|
blocks = collect(root)
|
||||||
|
if not blocks:
|
||||||
|
print("диаграмм нет")
|
||||||
|
return OK
|
||||||
|
|
||||||
|
cmd = renderer()
|
||||||
|
if cmd is None:
|
||||||
|
print("окружение: нет ни mmdc, ни npx — рендер невозможен."
|
||||||
|
" Поставь mermaid-cli (npm i -g @mermaid-js/mermaid-cli)"
|
||||||
|
" или запусти проверку там, где есть npx.", file=sys.stderr)
|
||||||
|
return ENV
|
||||||
|
|
||||||
|
broken: list[tuple[Block, str]] = []
|
||||||
|
with tempfile.TemporaryDirectory(prefix="diagrams-") as tmp:
|
||||||
|
workdir = Path(tmp)
|
||||||
|
config = workdir / "puppeteer.json"
|
||||||
|
config.write_text(json.dumps({"args": ["--no-sandbox"]}), encoding="utf-8")
|
||||||
|
for block in blocks:
|
||||||
|
error = render(cmd, block, workdir, config)
|
||||||
|
if error is not None:
|
||||||
|
broken.append((block, error))
|
||||||
|
|
||||||
|
files = len({b.path for b in blocks})
|
||||||
|
print(f"диаграмм {len(blocks)} в {files} файлах")
|
||||||
|
if broken:
|
||||||
|
print()
|
||||||
|
for block, error in broken:
|
||||||
|
print(f"НЕ РЕНДЕРИТСЯ {block.where}\n {error}")
|
||||||
|
print(f"\nИтог: не рендерятся {len(broken)}."
|
||||||
|
f" Блок правится в самом markdown — картинок в репозитории нет"
|
||||||
|
f" и не заводится, диаграмма живёт исходником рядом с текстом.")
|
||||||
|
return DRIFT
|
||||||
|
|
||||||
|
print("все рендерятся")
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(INTERNAL)
|
||||||
|
except Exception as e: # noqa: BLE001 — последний рубеж, код 4 по словарю
|
||||||
|
print(f"внутренний сбой ({type(e).__name__}): {e}", file=sys.stderr)
|
||||||
|
sys.exit(INTERNAL)
|
||||||
Reference in New Issue
Block a user