Files
transcriber/Taskfile.yml
T
av bc5c35790e Гейт ищет гонки, переписанные шаги схемы и девять новых классов дефектов
- включены noctx, contextcheck и bodyclose (отмена доходит до внешнего вызова,
  контекст приезжает сверху, тело ответа закрывается), nilerr, rowserrcheck и
  sqlclosecheck (отказ не теряется молча), testifylint и nolintlint (форма
  утверждения и форма подавления), плюс errcheck check-type-assertions:
  непроверенное приведение типа паникует, и check-blank его не видит
- заведён шаг tests: go test -race, потому что «результат пишет только держатель
  захвата» — утверждение об одновременности. Без компилятора C шаг гоняет тесты
  без детектора и краснеет кодом 3 после них: гонки не повод отнимать у гейта
  сами тесты
- заведён шаг migrations: у файла шага схемы допустим один статус — A. Баз диффа
  две, BASE и HEAD: первая отвечает на «уже уехал» настолько, насколько свежа
  origin/master, вторая ловит правку закоммиченного шага независимо от неё.
  Каталог берётся из docs/.docs.json, пустой каталог роняет шаг
- единственное подавление — noctx на httptest.NewRequest в проверках: за
  фикстурой запроса внешнего собеседника нет. Граница проверена мутацией —
  http.Get и exec.Command из проверки правилу по-прежнему подсудны
2026-08-13 10:28:12 +03:00

232 lines
13 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# https://taskfile.dev
version: '3'
vars:
PROJECT: "transcriber"
# База диффа для шагов, которым нужна разница с основной веткой.
BASE: '{{.BASE | default "origin/master"}}'
# Пути скриптов проверки. Умолчания — канонические пути маркетплейса, чтобы
# переустановка плагина не меняла Taskfile. Шаги независимы: каждый ловит
# дрейф своего каталога, и выпадение одного не подменяется другим.
#
# Недостающий скрипт — отказ окружения у всех обёрток ниже, и код у него 3 по
# общему словарю (CLAUDE.md, раздел «Гейт»). Прежний код 1 значил «дрейф» и
# отправлял читателя искать разъехавшееся там, где просто неполно дерево. Сам
# `task` отдаёт наружу свой 201 на любой отказ шага, поэтому словарь читается
# по коду скрипта, а не по коду `task`.
DOCS_PY: '{{.DOCS_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev-docs/skills/canon/scripts/docs.py"}}'
TASKS_PY: '{{.TASKS_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev-tasks/skills/tasks/scripts/tasks.py"}}'
OPENSPEC_PY: '{{.OPENSPEC_PY | default "~/.claude/plugins/marketplaces/av-dev-skills/av-dev-code/skills/openspec/scripts/openspec.py"}}'
tasks:
gate:
desc: 'Все проверки разом. База диффа: task gate BASE=<rev>'
cmds:
- go build ./...
- go vet ./...
- |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "$unformatted"
echo "gofmt: файлы выше не отформатированы"
exit 1
fi
- task: tests
- golangci-lint run
- task: shell
- task: dockerfile
- task: go-version
- task: migrations
- task: docs
- task: tasks
- task: openspec
# Последним: единственный шаг, которому нужна сеть, и самый долгий.
- task: vulns
tests:
desc: 'Тесты с детектором гонок'
cmds:
# Гонки ищет детектор, а не чтение кода: у сервиса три воркера ходят в одну
# очередь, и «результат пишет только держатель захвата» — утверждение о
# одновременном доступе. Детектору нужен CGO и компилятор C; сборка
# приложения по-прежнему обходится без них (CLAUDE.md, «Стек»), поэтому их
# отсутствие — отказ окружения, код 3, а не отказ проверки.
# Окружение проверяется **после** обычного прогона, а не вместо него:
# отсутствие компилятора отнимает у гейта поиск гонок, но не должно
# отнимать сами тесты. Порядок проверок — сперва компилятор: без него
# совет «включи CGO_ENABLED=1» бесполезен.
- |
if ! command -v gcc >/dev/null 2>&1 && ! command -v clang >/dev/null 2>&1; then
go test ./... || exit 1
echo "тесты прошли, но гонки не искали: детектору нужен компилятор C"
echo "ни gcc, ни clang не найдены в PATH; поставь: apt install gcc"
exit 3
fi
if [ "$(go env CGO_ENABLED)" != "1" ]; then
go test ./... || exit 1
echo "тесты прошли, но гонки не искали: детектору нужен CGO"
echo "CGO_ENABLED=$(go env CGO_ENABLED); включи: CGO_ENABLED=1 task gate"
exit 3
fi
go test -race ./...
migrations:
desc: 'Применённый шаг схемы не переписывается'
cmds:
# Инвариант CLAUDE.md (critical): хранилище считает применённое по имени
# файла шага, поэтому изменить уехавший шаг нельзя — только добавить новый.
# Компилятор этого не держит, и до этого шага не держало ничто.
#
# Судится каталог шагов против базы диффа: у файла шага допустим один
# статус — `A`. Правка (`M`), удаление (`D`) и переименование (`R`) красят.
# `migrations.go` под правило не подпадает: строка `Register` у нового шага
# прибавляется именно там, и запрет на него запретил бы заведение шага.
- |
if ! git rev-parse --verify --quiet "{{.BASE}}" >/dev/null 2>&1; then
echo "база диффа не найдена: {{.BASE}}"
echo "задай свою: task migrations BASE=<rev>"
exit 3
fi
# Каталог шагов берётся из docs/.docs.json — там он уже записан ключом
# `migrations` для сверки документов. Свой литерал завёл бы факту второй
# дом: каталог переехал бы, а один из двух стражей молча позеленел.
dir=$(python3 -c 'import json,sys; print(json.load(open("docs/.docs.json"))["migrations"])' 2>/dev/null) || dir=""
if [ -z "$dir" ] || [ ! -d "$dir" ]; then
echo "каталог шагов схемы не найден: ключ migrations в docs/.docs.json → '$dir'"
exit 3
fi
# Страж предмета: правило, потерявшее файлы, стало бы вечно зелёным от
# одного переименования — тот же приём, что у правил `internal/archrules`.
if [ -z "$(ls "$dir" | grep -E '^[0-9]{12}_.*\.go$')" ]; then
echo "в $dir нет ни одного файла шага: правило потеряло предмет"
echo "поправь шаблон имени в этом шаге либо ключ migrations в docs/.docs.json"
exit 3
fi
# Баз две, и вторая обязательна. `{{.BASE}}` отвечает на «шаг уже уехал»
# ровно настолько, насколько свеж `origin/master`: отставшая ссылка
# читает весь каталог как добавленный, и правило молчит. `HEAD` ловит
# правку закоммиченного шага в рабочем дереве независимо от ссылки.
for base in {{.BASE}} HEAD; do
touched=$(git diff --name-status "$base" -- "$dir" \
| grep -E '[0-9]{12}_[^/]*\.go$' \
| grep -vE '^A[[:space:]]' || true)
if [ -n "$touched" ]; then
echo "база $base:"
echo "$touched"
echo "применённый шаг схемы переписан: изменение схемы — только новым файлом шага"
echo "(CLAUDE.md, «Инварианты», critical: хранилище считает применённое по имени файла)"
exit 1
fi
done
shell:
desc: 'shellcheck на скрипты оболочки'
cmds:
# Скриптов два и оба свои: шаг сверки версий и `docker/entrypoint.sh`.
# Второй в образ копируется, но не исполняется — `ENTRYPOINT` в
# `Dockerfile` закомментирован, — и проверяется он именно поэтому: код,
# который никто не гоняет, портится незаметно. Ни один из двух не виден ни
# `go vet`, ни `golangci-lint`.
- |
if ! command -v shellcheck >/dev/null 2>&1; then
echo "shellcheck не найден в PATH"
echo "поставь: apt install shellcheck (или https://github.com/koalaman/shellcheck)"
exit 3
fi
shellcheck scripts/check-go-version.sh docker/entrypoint.sh
dockerfile:
desc: 'hadolint на Dockerfile'
cmds:
# DL3007 (`alpine:latest` у рантайм-слоя) подавлен: это открытая задача
# `pin-runtime-image-base`, и до её решения шаг краснел бы на известном.
# DL3018 (закрепить версии пакетов `apk`) подавлен по существу: alpine не
# держит старые версии в репозитории, и закрепление ломает сборку через
# недели — то есть лечение хуже болезни.
- |
if ! command -v hadolint >/dev/null 2>&1; then
echo "hadolint не найден в PATH"
echo "поставь: https://github.com/hadolint/hadolint/releases"
exit 3
fi
hadolint --ignore DL3007 --ignore DL3018 Dockerfile
go-version:
desc: 'Одна версия Go в go.mod, Dockerfile, CLAUDE.md и README.md'
cmds:
# Скрипт лежит в самом репозитории, а не в плагине: его отсутствие значит
# сломанное дерево, а не непоставленный плагин, и переопределять путь
# нечем и незачем. Код отсутствия — 3, как у прочих обёрток.
- |
py=scripts/check-go-version.sh
if [ ! -f "$py" ]; then
echo "$py не найден: дерево репозитория неполно"
exit 3
fi
sh "$py"
docs:
desc: 'Раскладка docs/ против канона'
cmds:
# Шаг обязан краснеть внятно, если скрипта нет, а не пропускаться молча.
- |
py=$(eval echo {{.DOCS_PY}})
if [ ! -f "$py" ]; then
echo "docs.py не найден: $py"
echo "поставь плагин av-dev-docs либо задай путь: task docs DOCS_PY=<путь>"
exit 3
fi
python3 "$py" check --base {{.BASE}}
tasks:
desc: 'Согласованность каталога задач'
cmds:
- |
py=$(eval echo {{.TASKS_PY}})
if [ ! -f "$py" ]; then
echo "tasks.py не найден: $py"
echo "поставь плагин av-dev-tasks либо задай путь: task tasks TASKS_PY=<путь>"
exit 3
fi
python3 "$py" check --dir tasks
openspec:
desc: 'Форма openspec/config.yaml'
cmds:
- |
py=$(eval echo {{.OPENSPEC_PY}})
if [ ! -f "$py" ]; then
echo "openspec.py не найден: $py"
echo "поставь плагин av-dev-code либо задай путь: task openspec OPENSPEC_PY=<путь>"
exit 3
fi
python3 "$py" check --dir .
vulns:
desc: 'Достижимые из кода уязвимости в зависимостях'
cmds:
# `govulncheck` — внешний инструмент, а не плагин и не файл репозитория:
# ставится `go install golang.org/x/vuln/cmd/govulncheck@latest`. Его
# отсутствие — отказ окружения, код 3, как у прочих обёрток.
#
# Свой код 3 у самого инструмента значит «уязвимость найдена» и с кодом
# обёртки совпадает; различает их сообщение — обёртка называет недостающий
# инструмент. Шагу нужна сеть: база уязвимостей живёт на vuln.go.dev, и без
# сети шаг краснеет, а не пропускается молча.
- |
if ! command -v govulncheck >/dev/null 2>&1; then
echo "govulncheck не найден в PATH"
echo "поставь: go install golang.org/x/vuln/cmd/govulncheck@latest"
exit 3
fi
govulncheck ./...
# Контракт роли app_image (pet-project-server): собрать полный образ и затегать
# его $BUILD_ID. Деплой целиком: `inv pl -- transcriber` в pet-project-server.
image:
desc: 'Docker-образ. Тег из $BUILD_ID (по умолчанию dev)'
cmds:
- docker build --pull --file Dockerfile --tag "{{.PROJECT}}:${BUILD_ID:-dev}" .