mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 04:38:55 +00:00
feat(process): nightly ship batch review, reused by the beta gate by patch set (#727)
Ship tasks merge without a model review and their code was first read by the batch review right before a beta: one session over the whole range, ten to forty-five minutes on the release path, days after the merge. The gate also knew a single document (SHIP-REVIEW-<tag>.md) and covered tasks by number only, so a commit that landed after the review under the same trailer still counted as read. - scripts/ship-review.mjs: the patch set of a task is the sorted `git patch-id --stable` of its range commits, without `Release:` commits (the beta candidate carries every Issue: of the line) and commits touching only docs/reviews/**; the diff options are explicit so a local git config cannot change it. shipCoverage rates every ship task from the documents of the same base (candidate and origin/dev, latest publication wins): clean, high, stale, none; documents without `patches` cover by number. `tag=nightly` is a reserved mode: the candidate is required, the document is SHIP-REVIEW-<base>-dev-<sha12>.md, only none/stale tasks are read and nothing runs when nothing is uncovered. The beta reads the same delta (force=true reads everything, as before); the brief names what the night already read. The gate refuses none/stale with the command and keeps the High refusal with force=true; all clean passes without a tag document. The machine block gains `mode` and `patches` at its end. comment-high writes one line per task of a nightly document with High, once per document (hp:ship-review-high). - _ship-review.yml: prepare refuses nightly without a candidate before defaulting to the dev tip, computes the document from base and SHA and no longer reads a prepare failure behind `| tee` as "no ship tasks"; publish takes mode and patches from prepare, never from the model result; a new step comments High at night with HP_PROCESS_TOKEN. - _nightly.yml: the Validate run SHA is a separate step output before the wait; a new job dispatches ship-review.yml -f tag=nightly on it whatever Validate's outcome, waits only for the run to appear and never colours the night. Thin files in main are unchanged. - reviews-index/reviews-archive: the nightly name is a ship document with nightly: true; a beta base archives with its line, a stable base with the nearest archived line newer than the base, or stays. - PROCESS.md §11.7, §10.4 and REVIEWER.md describe the nightly mode, patch set, coverage and beta delta; the digest test pins the key rule. Tests run the prepare, publish and comment steps and the nightly steps on real bash with real git in temporary repositories; only push transport and gh are faked. Issue: #727 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -18,6 +18,11 @@
|
||||
# успехом в момент постановки Validate в очередь, и красный полный прогон не
|
||||
# делал ночной workflow красным. Теперь job находит запущенный прогон и ждёт
|
||||
# его: успешный dispatch — не успешная проверка.
|
||||
#
|
||||
# #727: после Validate при любом его исходе ночь запускает пакетное ревью ship
|
||||
# (`ship-review.yml -f tag=nightly`) на SHA прогона Validate: оно читает код, а
|
||||
# не прогон. Ship-задача узнаёт о дефектах в ночь после слияния, а бета читает
|
||||
# только то, что ночь не прочла (PROCESS.md §11.7). Цвет ночи — цвет Validate.
|
||||
name: "Ночной полный прогон dev · тело (#623)"
|
||||
|
||||
on:
|
||||
@@ -34,6 +39,9 @@ jobs:
|
||||
name: "Запустить Validate на dev с полным набором и дождаться результата"
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 90
|
||||
outputs:
|
||||
# #727: SHA прогона Validate — отдельным шагом до ожидания, красный Validate его не теряет.
|
||||
head_sha: ${{ steps.validate.outputs.head_sha }}
|
||||
steps:
|
||||
# #658: при плане 02:30 UTC ночь фактически стартовала в 07:42–08:05 и
|
||||
# кончалась в рабочее время владельца. Сдвиг старта больше часа — видимое
|
||||
@@ -55,7 +63,9 @@ jobs:
|
||||
if [ "$lag" -gt 60 ]; then
|
||||
echo "::warning::ночной прогон стартовал на $lag мин позже расписания ($SCHEDULE UTC) — очередь расписаний GitHub (#658)"
|
||||
fi
|
||||
- env:
|
||||
- name: "Запустить Validate и найти его прогон"
|
||||
id: validate
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
@@ -80,5 +90,62 @@ jobs:
|
||||
url="${{ github.server_url }}/$REPO/actions/runs/$run_id"
|
||||
echo "дочерний прогон: $url"
|
||||
echo "- Validate(dev, full=true): $url" >> "$GITHUB_STEP_SUMMARY"
|
||||
head_sha=$(gh run view "$run_id" --repo "$REPO" --json headSha --jq .headSha)
|
||||
echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
|
||||
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
|
||||
- name: "Дождаться Validate"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
RUN_ID: ${{ steps.validate.outputs.run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
run_id="$RUN_ID"
|
||||
# Ждём завершения; красный дочерний прогон — красный ночной.
|
||||
gh run watch "$run_id" --repo "$REPO" --exit-status --interval 30
|
||||
|
||||
# #727 К6: ночное пакетное ревью ship на SHA прогона Validate — при любом его
|
||||
# исходе. Dispatch тонкого `ship-review.yml` (`github.token` порождает прогон,
|
||||
# как у Validate; входы и права тонких файлов уже в main). Job ждёт только
|
||||
# появления прогона, не его конца; не появился — предупреждение, не красная
|
||||
# ночь: цвет ночи — цвет Validate.
|
||||
ship_review:
|
||||
name: "Запустить ночное пакетное ревью ship на SHA прогона Validate"
|
||||
needs: dispatch
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: "Запустить ship-ревью и дождаться появления прогона"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
CANDIDATE: ${{ needs.dispatch.outputs.head_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$CANDIDATE" ]; then
|
||||
echo "::warning::нет SHA прогона Validate — ночное ship-ревью не запущено"
|
||||
echo "- Ship-ревью ночью: не запущено — нет SHA прогона Validate" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
since=$(date -u +%FT%TZ)
|
||||
if ! gh workflow run ship-review.yml --ref dev -f tag=nightly -f candidate="$CANDIDATE" --repo "$REPO"; then
|
||||
echo "::warning::ship-ревью ночью не запущено: dispatch отклонён — ночь не красится"
|
||||
echo "- Ship-ревью ночью: dispatch отклонён" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
run_id=""
|
||||
for _ in $(seq 1 18); do
|
||||
sleep 10
|
||||
run_id=$(gh run list --repo "$REPO" --workflow ship-review.yml --branch dev \
|
||||
--event workflow_dispatch --json databaseId,createdAt --limit 5 \
|
||||
--jq "[.[] | select(.createdAt >= \"$since\")] | sort_by(.createdAt) | last | .databaseId // empty")
|
||||
[ -n "$run_id" ] && break
|
||||
done
|
||||
if [ -z "$run_id" ]; then
|
||||
echo "::warning::прогон ship-ревью не появился за 3 минуты — ночь не красится, её цвет — цвет Validate"
|
||||
echo "- Ship-ревью ночью (\`$CANDIDATE\`): прогон не появился за 3 минуты" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
echo "- Ship-ревью ночью (\`$CANDIDATE\`): ${{ github.server_url }}/$REPO/actions/runs/$run_id" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
@@ -8,6 +8,11 @@ name: "Бета: пакетное ревью ship · тело (#716)"
|
||||
#
|
||||
# Три job, как у release-review.yml (#551, #556): детерминированный вход,
|
||||
# модель без права записи, детерминированная публикация документа в `dev`.
|
||||
#
|
||||
# #727: `tag=nightly` — ночной режим. Его запускает `_nightly.yml` после
|
||||
# полного Validate с `candidate` = голова `dev` того прогона. Читаются только
|
||||
# ship-задачи без покрытия или изменившиеся после ревью (`shipCoverage`), документ —
|
||||
# `SHIP-REVIEW-<база>-dev-<sha12>.md`. Бета так же читает только дельту.
|
||||
|
||||
on:
|
||||
# #716: тело вызывается тонким файлом `ship-review.yml` по ссылке `@dev`;
|
||||
@@ -15,6 +20,7 @@ on:
|
||||
# файла в ветке по умолчанию GitHub не даёт запустить workflow_dispatch вовсе.
|
||||
workflow_call:
|
||||
inputs:
|
||||
# #727: `nightly` — зарезервированное значение (ночной режим, candidate обязателен).
|
||||
tag:
|
||||
description: "Beta tag the review is for, for example v1.79.0-beta.1"
|
||||
required: true
|
||||
@@ -49,6 +55,9 @@ jobs:
|
||||
base: ${{ steps.range.outputs.base }}
|
||||
doc: ${{ steps.range.outputs.doc }}
|
||||
issues: ${{ steps.range.outputs.issues }}
|
||||
mode: ${{ steps.range.outputs.mode }}
|
||||
patches: ${{ steps.range.outputs.patches }}
|
||||
subject: ${{ steps.range.outputs.subject }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
@@ -69,24 +78,31 @@ jobs:
|
||||
FORCE: ${{ inputs.force }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
run: |
|
||||
doc=$(node scripts/ship-review.mjs doc --tag="$TAG")
|
||||
# Отказ prepare за `| tee` не должен читаться как «ship-задач нет» (#727).
|
||||
set -o pipefail
|
||||
git fetch -q --tags origin
|
||||
# #727: tag=nightly без candidate — отказ до подстановки вершины dev.
|
||||
node scripts/ship-review.mjs mode --tag="$TAG" --candidate="$CANDIDATE"
|
||||
if [ -z "$CANDIDATE" ]; then CANDIDATE=$(git rev-parse origin/dev); fi
|
||||
git cat-file -e "$CANDIDATE^{commit}"
|
||||
CANDIDATE=$(git rev-parse "$CANDIDATE^{commit}")
|
||||
doc=$(node scripts/ship-review.mjs doc --tag="$TAG" --candidate="$CANDIDATE")
|
||||
if [ "$FORCE" != "true" ] && git cat-file -e "origin/dev:$doc" 2>/dev/null; then
|
||||
echo "::notice::$doc уже есть в dev — повторное ревью не запускается (force=true, чтобы переснять)"
|
||||
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
out="$RUNNER_TEMP/ship-review-input"
|
||||
node scripts/ship-review.mjs prepare --tag="$TAG" --candidate="$CANDIDATE" \
|
||||
node scripts/ship-review.mjs prepare --tag="$TAG" --candidate="$CANDIDATE" --force="$FORCE" \
|
||||
--out="$out" --repo="${{ github.repository }}" --run-url="$RUN_URL" | tee "$RUNNER_TEMP/range.txt"
|
||||
cat "$RUNNER_TEMP/range.txt" >> "$GITHUB_OUTPUT"
|
||||
echo "candidate=$CANDIDATE" >> "$GITHUB_OUTPUT"
|
||||
issues=$(sed -n 's/^issues=//p' "$RUNNER_TEMP/range.txt")
|
||||
if [ -z "$issues" ]; then
|
||||
echo "::notice::ship-задач в диапазоне нет — ревью не нужно"
|
||||
# #727 К5: пустая дельта — все ship-задачи покрыты документами базы.
|
||||
note=$(sed -n 's/^note=//p' "$RUNNER_TEMP/range.txt")
|
||||
echo "::notice::${note:-ship-задач в диапазоне нет — ревью не нужно}"
|
||||
if [ -n "$note" ]; then echo "- $note" >> "$GITHUB_STEP_SUMMARY"; fi
|
||||
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
@@ -183,8 +199,9 @@ jobs:
|
||||
prompt: |
|
||||
Ты ревьюер кода проекта House Plan. Язык ответа — русский.
|
||||
|
||||
Бета: ${{ inputs.tag }} · кандидат ${{ needs.prepare.outputs.candidate }}
|
||||
(рабочая копия уже на нём) · база диапазона: ${{ needs.prepare.outputs.base || 'нет' }}.
|
||||
${{ needs.prepare.outputs.subject }}
|
||||
Кандидат ${{ needs.prepare.outputs.candidate }} (рабочая копия уже на нём) · база
|
||||
диапазона: ${{ needs.prepare.outputs.base || 'нет' }}.
|
||||
Задачи: ${{ needs.prepare.outputs.issues }} — вход в $REVIEW_INPUT/brief.md
|
||||
(строка ТЗ каждой задачи и её коммиты) и $REVIEW_INPUT/ship-issues.json.
|
||||
|
||||
@@ -271,7 +288,8 @@ jobs:
|
||||
path: ${{ runner.temp }}/ship-review-result
|
||||
|
||||
# Модель пишет только текст. Путь, машинный блок (его читает гейт беты),
|
||||
# индекс и коммит решает этот шаг.
|
||||
# индекс и коммит решает этот шаг. #727: `mode` и `patches` блока — из
|
||||
# prepare (детерминированный патч-набор), не из результата модели.
|
||||
- name: Опубликовать документ
|
||||
env:
|
||||
TOKEN: ${{ secrets.HP_PROCESS_TOKEN }}
|
||||
@@ -280,11 +298,13 @@ jobs:
|
||||
CANDIDATE: ${{ needs.prepare.outputs.candidate }}
|
||||
BASE: ${{ needs.prepare.outputs.base }}
|
||||
ISSUES: ${{ needs.prepare.outputs.issues }}
|
||||
MODE: ${{ needs.prepare.outputs.mode }}
|
||||
PATCHES: ${{ needs.prepare.outputs.patches }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
run: |
|
||||
dir="$RUNNER_TEMP/ship-review-result"
|
||||
(cd "$dir" && sha256sum -c manifest.sha256)
|
||||
test "$DOC" = "$(node scripts/ship-review.mjs doc --tag="$TAG")"
|
||||
test "$DOC" = "$(node scripts/ship-review.mjs doc --tag="$TAG" --candidate="$CANDIDATE")"
|
||||
high=$(jq -r '.high' "$dir/result.json")
|
||||
medium=$(jq -r '.medium' "$dir/result.json")
|
||||
low=$(jq -r '.low' "$dir/result.json")
|
||||
@@ -301,7 +321,7 @@ jobs:
|
||||
tag: process.env.TAG, candidate: process.env.CANDIDATE, base: process.env.BASE || null,
|
||||
issues: String(process.env.ISSUES || "").split(",").filter(Boolean).map(Number),
|
||||
high: Number(process.argv[1]), medium: Number(process.argv[2]), low: Number(process.argv[3]),
|
||||
runUrl: process.env.RUN_URL,
|
||||
runUrl: process.env.RUN_URL, mode: process.env.MODE, patches: m.parsePatches(process.env.PATCHES),
|
||||
})));
|
||||
' "$high" "$medium" "$low"
|
||||
} > "$DOC"
|
||||
@@ -331,3 +351,19 @@ jobs:
|
||||
done
|
||||
echo "::error::документ ревью не опубликован в dev за три попытки"
|
||||
exit 1
|
||||
|
||||
# #727 К8: High ночного документа — одна строка в каждую его задачу.
|
||||
# Повтор на тот же документ не пишется (метка hp:ship-review-high doc=…),
|
||||
# Medium и Low в issue не идут — их решает владелец (§11.7). Режим и
|
||||
# High проверяет и скрипт: шаг вне ночи или без High ничего не пишет.
|
||||
- name: High ночью — строка в задачи документа
|
||||
if: needs.prepare.outputs.mode == 'nightly'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }}
|
||||
MODE: ${{ needs.prepare.outputs.mode }}
|
||||
DOC: ${{ needs.prepare.outputs.doc }}
|
||||
ISSUES: ${{ needs.prepare.outputs.issues }}
|
||||
run: |
|
||||
high=$(jq -r '.high' "$RUNNER_TEMP/ship-review-result/result.json")
|
||||
node scripts/ship-review.mjs comment-high --mode="$MODE" --high="$high" --doc="$DOC" \
|
||||
--issues="$ISSUES" --repo="${{ github.repository }}"
|
||||
|
||||
Reference in New Issue
Block a user