Files
houseplan-card/.github/workflows/process.yml
T
Matysh 495a99872b ci: event-driven process pipeline for spec and code review
Adds .github/workflows/process.yml. A status label change is the trigger:
S4-spec-review runs the spec review, S7-code-review runs the code review,
and the verdict decides the next label. Only a green verdict advances;
yellow and red return the task to its author. Cycle limits (4, or 2 on the
light track) are counted from the verdicts already posted on the issue.

Labels are moved with HP_PROCESS_TOKEN, not GITHUB_TOKEN, so the change
emits an event and the chain continues.

Issue: #114
User-Visible: no
2026-08-13 11:33:21 +03:00

207 lines
12 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.
name: Process
# Событийный конвейер процесса (PROCESS.md). Смена статусной метки — это
# сообщение: она порождает событие, событие запускает следующий шаг.
#
# S4-spec-review -> ревью ТЗ -> S5-ready | S3-spec
# S7-code-review -> код-ревью -> S8-merged | S6-in-progress
#
# Две вещи, без которых конвейер молча не работает:
#
# 1. Метки переставляются токеном HP_PROCESS_TOKEN, а не GITHUB_TOKEN. GitHub
# намеренно не запускает workflow от событий, вызванных GITHUB_TOKEN, чтобы
# не было циклов — цепочка оборвалась бы после первого шага.
# 2. Этот файл обязан лежать в ветке по умолчанию (main). Для события `issues`
# GitHub берёт workflow только оттуда, независимо от того, что в dev.
on:
issues:
types: [labeled]
concurrency:
# Два события по одному issue не должны запускать два прогона.
group: process-issue-${{ github.event.issue.number }}
cancel-in-progress: false
permissions:
contents: read
issues: write
jobs:
guard:
runs-on: ubuntu-latest
outputs:
stage: ${{ steps.decide.outputs.stage }}
cycle: ${{ steps.decide.outputs.cycle }}
limit: ${{ steps.decide.outputs.limit }}
steps:
- id: decide
env:
GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }}
LABEL: ${{ github.event.label.name }}
AUTHOR: ${{ github.event.issue.user.login }}
BLOCKED: ${{ contains(github.event.issue.labels.*.name, 'blocked') }}
EXHAUSTED: ${{ contains(github.event.issue.labels.*.name, 'review-4') }}
SMALL: ${{ contains(github.event.issue.labels.*.name, 'small') }}
NUM: ${{ github.event.issue.number }}
run: |
stage=""
# Лимит циклов: 4 обычный, 2 на лёгком треке (PROCESS.md §4).
limit=4; [ "$SMALL" = "true" ] && limit=2
# Счётчик — по числу уже опубликованных вердиктов в issue.
done_cycles=$(gh issue view "$NUM" --repo "${{ github.repository }}" \
--json comments -q '[.comments[] | select(.body | test("Вердикт:"))] | length')
# В процесс идут только issue, созданные владельцем: репозиторий
# публичный, чужие отчёты бывают невалидны и статусов не несут.
if [ "$AUTHOR" != "Matysh" ]; then
echo "issue от $AUTHOR, не от владельца — пропуск"
elif [ "$BLOCKED" = "true" ]; then
echo "стоит blocked — конвейер не запускается"
elif [ "$EXHAUSTED" = "true" ]; then
echo "стоит review-4 — решение за владельцем"
elif [ "$done_cycles" -ge "$limit" ]; then
echo "циклов пройдено $done_cycles из $limit — лимит исчерпан"
gh issue edit "$NUM" --repo "${{ github.repository }}" --add-label review-4
gh issue comment "$NUM" --repo "${{ github.repository }}" --body \
"Лимит циклов ревью исчерпан ($done_cycles из $limit). Пятого захода нет: решение владельца — разделить задачу, отклонить или арбитраж (PROCESS.md §4)."
else
case "$LABEL" in
S4-spec-review) stage="spec" ;;
S7-code-review) stage="code" ;;
*) echo "метка $LABEL конвейер не запускает" ;;
esac
fi
echo "stage=$stage" >> "$GITHUB_OUTPUT"
echo "cycle=$((done_cycles + 1))" >> "$GITHUB_OUTPUT"
echo "limit=$limit" >> "$GITHUB_OUTPUT"
review:
needs: guard
if: needs.guard.outputs.stage != ''
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: dev
- uses: actions/setup-node@v4
with: { node-version: 22 }
- name: Review
id: review
uses: anthropics/claude-code-action@v1
with:
# Подписка, а не отдельный счёт API: токен выпускается через
# `claude setup-token` (Pro/Max). Действуют лимиты подписки.
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
Ты ревьюер проекта House Plan. Язык ответа — русский.
Issue: #${{ github.event.issue.number }}
Репозиторий: ${{ github.repository }}
Этап: ${{ needs.guard.outputs.stage }}
spec — ревью ТЗ (PROCESS.md §2.4)
code — код-ревью (PROCESS.md §2.7)
Прочитай в этом порядке, прежде чем судить:
1. docs/SCOPE.md — зачем продукт существует и для кого. Он
ограничитель: «features are built, improved and accepted only
if they serve a job listed here». Первый вопрос к задаче —
какую строку Core user jobs она закрывает.
2. AGENTS.md и PROCESS.md — процесс, классы изменений, трейлеры,
лимит циклов, формат вердикта.
3. Тело issue #${{ github.event.issue.number }} и все комментарии.
4. Если меняется видимое поведение — docs/USER-GUIDE.ru.md:
терминология интерфейса берётся оттуда, а не изобретается.
5. Канонический документ затронутой подсистемы: docs/SUN.md,
LIGHT.md, CANVAS.md, WALL-THICKNESS.md, UX-MODES.md,
CONFIG-COMPATIBILITY.md, TOUCH-SUPPORT.md.
Для этапа spec: если issue помечен small, ТЗ живёт в теле issue и
файла в docs/specs/ быть не должно. Иначе ТЗ — docs/specs/<NN>-*.md.
Проверь обязательные разделы §7.1, однозначность каждого AC и
указание способа доказательства. Отдельно проверь, что автор не
выдал догадку за решение: утверждение о поведении, которого нет ни
в одном документе и которое не помечено как предположение, —
замечание. Не бывает сложной задачи без единого открытого вопроса.
Для этапа code: материал — diff по issue. Ручного тестирования в
цикле нет, поэтому именно ты отвечаешь на вопрос «оно вообще
работает». По каждому AC: либо он доказан автотестом и ты убедился,
что тест умеет падать, либо разобран по коду с явной записью
«проверено чтением, не исполнением». «Verified» без названной
команды и её результата доказательством не является. Проверь
трейлеры Issue и User-Visible, при User-Visible: yes — правки в оба
changelog в том же коммите.
Ты НЕ правишь ни ТЗ, ни продуктовый код. Только оцениваешь.
Серьёзность: High блокирует; Medium обязан стать отдельным issue;
Low либо правится, либо снимается с записью. Жёлтый вердикт
допустим при полностью выполненных AC, если изменение не решает
заявленный сценарий или ухудшает смежный. Продуктовое рассуждение
расширяет вопросы, но не отменяет AC и не даёт права менять скоуп.
Каждую Medium-находку заведи отдельным issue со ссылкой на
#${{ github.event.issue.number }} и метками: тип, приоритет,
S1-new. «Оставили в тексте ревью» закрытием не считается и прямо
запрещено §12.
Оставь в issue комментарий с разбором: находки с воспроизведением,
что проверено и корректно, чего не проверял. Первой строкой —
вердикт в формате §7.2:
`Вердикт: зелёный/жёлтый/красный · цикл r${{ needs.guard.outputs.cycle }}/${{ needs.guard.outputs.limit }} · High: N · Medium: N → #…`
Затем верни JSON по схеме.
claude_args: |
--max-turns 40
--allowedTools Read,Grep,Glob,Bash,mcp__github__add_issue_comment,mcp__github__issue_write,mcp__github__issue_read
--json-schema '{"type":"object","properties":{"verdict":{"type":"string","enum":["green","yellow","red"]},"high":{"type":"integer"},"medium":{"type":"integer"},"summary":{"type":"string"}},"required":["verdict","high","medium","summary"]}'
- name: Переставить метку
env:
# Именно PAT: с GITHUB_TOKEN следующий шаг конвейера не запустится.
GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }}
OUT: ${{ steps.review.outputs.structured_output }}
STAGE: ${{ needs.guard.outputs.stage }}
NUM: ${{ github.event.issue.number }}
run: |
verdict=$(echo "$OUT" | jq -r '.verdict')
high=$(echo "$OUT" | jq -r '.high')
echo "вердикт: $verdict, High: $high"
# Вперёд двигает ТОЛЬКО зелёный.
#
# Жёлтый возвращает автору, и это не перестраховка. На первом живом
# прогоне (#111) жёлтый означал, что AC описывает неверное изменение
# контракта: реализовать такое ТЗ — сделать ошибку по инструкции.
# Разница между жёлтым и красным остаётся содержательной для человека
# и считается циклом, но ни один из них не пропускает дальше.
if [ "$verdict" = "green" ] && [ "$high" -eq 0 ]; then
case "$STAGE" in
spec) from=S4-spec-review; to=S5-ready ;;
code) from=S7-code-review; to=S8-merged ;;
esac
else
case "$STAGE" in
spec) from=S4-spec-review; to=S3-spec ;;
code) from=S7-code-review; to=S6-in-progress ;;
esac
fi
gh issue edit "$NUM" --repo "${{ github.repository }}" \
--add-label "$to" --remove-label "$from"
echo "$from -> $to"
- name: Позвать владельца, если ревью упало
if: failure()
env:
GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }}
run: |
gh issue comment "${{ github.event.issue.number }}" --repo "${{ github.repository }}" \
--body "Автоматическое ревью не отработало: [прогон](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). Статусная метка не менялась, задача осталась на месте."