From 26303adfec2502254e9f1ffe5fd172df1921d7f5 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sun, 16 Aug 2026 00:00:36 +0300 Subject: [PATCH 1/6] ci: move workflows to Node 24 actions Issue: #145 User-Visible: no --- .github/workflows/announce.yml | 2 +- .github/workflows/mutation-gate.yml | 6 ++-- .github/workflows/performance.yml | 8 ++--- .github/workflows/process.yml | 6 ++-- .github/workflows/publish-prerelease.yml | 10 +++--- .github/workflows/release-zip.yml | 2 +- .github/workflows/release.yml | 14 ++++---- .github/workflows/validate.yml | 42 ++++++++++++------------ 8 files changed, 45 insertions(+), 45 deletions(-) diff --git a/.github/workflows/announce.yml b/.github/workflows/announce.yml index 7b7bdd7c..66c6f39d 100644 --- a/.github/workflows/announce.yml +++ b/.github/workflows/announce.yml @@ -41,7 +41,7 @@ jobs: steps: - name: Check out release notes for a reusable call if: ${{ inputs.reusable == true }} - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ inputs.ref }} - name: Send to Telegram diff --git a/.github/workflows/mutation-gate.yml b/.github/workflows/mutation-gate.yml index 26491f73..b8c40c35 100644 --- a/.github/workflows/mutation-gate.yml +++ b/.github/workflows/mutation-gate.yml @@ -31,12 +31,12 @@ jobs: # нормально: гейт предрелизный. Час — потолок против зависшего Chromium. timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: dev fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm @@ -45,7 +45,7 @@ jobs: - name: Кэш браузеров Playwright id: pw - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 21a05acc..ee7d3a6b 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -30,7 +30,7 @@ jobs: timeout-minutes: 60 steps: - name: Check out candidate - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: path: candidate fetch-depth: 2 @@ -116,12 +116,12 @@ jobs: echo "Comparison base: $sha ($source)" >> "$GITHUB_STEP_SUMMARY" - name: Check out base SHA - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ steps.base.outputs.sha }} path: baseline - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm @@ -172,7 +172,7 @@ jobs: - name: Upload full performance reports if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: full-performance path: artifacts/performance diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index 274aaa59..dee8d2c4 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -131,7 +131,7 @@ jobs: # Время — единственный настоящий ограничитель зациклившегося прогона. timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 ref: dev @@ -141,7 +141,7 @@ jobs: # кэша, платились из бюджета 45 минут и из лимитов подписки, а ходы модели # тратились на работу инфраструктуры. В validate.yml кэш стоит на всех # тяжёлых job, здесь его не было. - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm @@ -174,7 +174,7 @@ jobs: # но когда нужен — качать его заново дороже, чем держать в кэше. - name: Кэш браузеров Playwright id: pw - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} diff --git a/.github/workflows/publish-prerelease.yml b/.github/workflows/publish-prerelease.yml index 9e5d7140..e2ae9382 100644 --- a/.github/workflows/publish-prerelease.yml +++ b/.github/workflows/publish-prerelease.yml @@ -24,11 +24,11 @@ jobs: sha: ${{ steps.candidate.outputs.sha }} tag: ${{ steps.candidate.outputs.tag }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: ${{ github.sha }} fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: { node-version: 22 } - name: Pin the current dev candidate id: candidate @@ -67,11 +67,11 @@ jobs: url: ${{ steps.verify.outputs.url }} newly_published: ${{ steps.release.outputs.newly_published }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: ${{ needs.gate.outputs.sha }} fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: { node-version: 22 } - name: Build and verify both release assets before publication env: @@ -173,7 +173,7 @@ jobs: printf '### Published %s\n\n- exact SHA: `%s`\n- [GitHub prerelease](%s)\n- assets: `houseplan-card.js`, `houseplan.zip`\n' \ "$TAG" "$SHA" "$URL" >> "$GITHUB_STEP_SUMMARY" - name: Verify HACS prerelease discovery order - uses: actions/github-script@v7 + uses: actions/github-script@v9 env: EXPECTED_TAG: ${{ needs.gate.outputs.tag }} with: diff --git a/.github/workflows/release-zip.yml b/.github/workflows/release-zip.yml index 6669fcad..c1547ecd 100644 --- a/.github/workflows/release-zip.yml +++ b/.github/workflows/release-zip.yml @@ -27,7 +27,7 @@ jobs: EVENT_TAG: ${{ github.event.release.tag_name }} INPUT_TAG: ${{ github.event.inputs.tag }} run: echo "tag=${EVENT_TAG:-$INPUT_TAG}" >> "$GITHUB_OUTPUT" - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: ${{ steps.tag.outputs.tag }} - name: Build houseplan.zip (contents of custom_components/houseplan at zip root) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 52f688a7..18e0be13 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,11 +16,11 @@ jobs: gate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: ${{ github.event.release.tag_name }} fetch-depth: 0 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: { node-version: 22 } - name: Require a green Validate for this exact commit env: @@ -47,10 +47,10 @@ jobs: needs: gate runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: ref: ${{ github.event.release.tag_name }} - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: { node-version: 22 } - run: npm ci && npm run build - name: Verify compositor frame continuity for a stable release @@ -61,13 +61,13 @@ jobs: npm run continuity:screencast - name: Upload failed continuity frames if: ${{ failure() && !github.event.release.prerelease }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: continuity-screencast path: artifacts/continuity-screencast - run: cp dist/houseplan-card.js custom_components/houseplan/frontend/ - name: Attach card to release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: dist/houseplan-card.js hacs-discovery: @@ -80,7 +80,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Verify the published tag is the prerelease HACS will discover - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const releases = await github.paginate(github.rest.repos.listReleases, { diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 83f8f9cb..e00d309f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -18,9 +18,9 @@ jobs: provenance: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: { fetch-depth: 0 } - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: { node-version: 22 } - name: Validate commit trailers and hook mode env: @@ -40,9 +40,9 @@ jobs: process-gate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: { fetch-depth: 0 } - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: { node-version: 22 } - name: Process gate env: @@ -71,7 +71,7 @@ jobs: backend: ${{ steps.classify.outputs.backend }} integration: ${{ steps.classify.outputs.integration }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: { fetch-depth: 0 } - id: classify env: @@ -110,7 +110,7 @@ jobs: if: needs.changes.outputs.integration == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: HACS validation uses: hacs/action@main with: @@ -121,7 +121,7 @@ jobs: if: needs.changes.outputs.integration == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Hassfest validation uses: home-assistant/actions/hassfest@master @@ -130,8 +130,8 @@ jobs: if: needs.changes.outputs.frontend == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm @@ -152,8 +152,8 @@ jobs: needs: frontend runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm @@ -179,7 +179,7 @@ jobs: exit $fail - name: Upload smoke logs if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: smoke-logs path: /tmp/smoke-logs @@ -190,8 +190,8 @@ jobs: needs: frontend runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm @@ -212,7 +212,7 @@ jobs: fi - name: Upload golden candidates/diffs if: failure() || steps.golden.outputs.has_baselines == 'false' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: golden-images path: artifacts/golden @@ -224,8 +224,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 with: node-version: 22 cache: npm @@ -242,7 +242,7 @@ jobs: npm run benchmark:compare -- --absolute-only --budgets=demo/performance/budgets-glow-smoke.json --candidate=artifacts/performance-smoke/candidate.json --output=artifacts/performance-smoke/comparison.json - name: Upload performance smoke report if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: performance-smoke path: artifacts/performance-smoke @@ -252,12 +252,12 @@ jobs: if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 # Browser fixtures are generated by their real ESM factories and then # validated through the Python CONFIG_SCHEMA/LAYOUT_SCHEMA in the same test. - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: { node-version: 22 } - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: { python-version: "3.13" } - run: pip install pytest voluptuous pytest-homeassistant-custom-component home-assistant-frontend - name: Backend unit tests (pure + HA harness) From 382afd27664e8f57f7d69dbde7ee5bf18fa2a2e3 Mon Sep 17 00:00:00 2001 From: Matysh Date: Tue, 18 Aug 2026 17:13:35 +0300 Subject: [PATCH 2/6] fix: verify the PAT before reviewing, pick the freshest task branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #150 reached a green verdict and then hit two pipeline defects at once. The review document push came back 403 as github-actions[bot]: the PAT had died, and checkout's persisted credential quietly took its place — a masked actor instead of a loud failure. Credentials are no longer persisted, and the token is now proven alive before the review starts, not after forty minutes of reviewer work. Branch selection took the first match alphabetically, and with a spec-era branch sitting next to the implementation branch that meant the stale one. The freshest branch by commit date is chosen instead, with a warning naming every candidate when more than one exists. Verified against the real #150 branches: the fix branch wins, the warning fires. Issue: #114 User-Visible: no --- .github/workflows/process.yml | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index dee8d2c4..97de391d 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -135,6 +135,26 @@ jobs: with: fetch-depth: 0 ref: dev + # Иначе в конфиге git остаётся креденшел GITHUB_TOKEN, и push с + # мёртвым PAT молча уходит от github-actions[bot] — 403 при + # contents: read. Отказ обязан быть громким и правильным. + persist-credentials: false + + # Живость PAT проверяется ДО ревью. На #150 истёкший токен обнаружился + # только на публикации документа — после сорока минут работы ревьюера. + - name: Секрет HP_PROCESS_TOKEN жив + env: + GH_TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} + run: | + if [ -z "$GH_TOKEN" ]; then + echo "::error::HP_PROCESS_TOKEN пуст — секрет удалён или недоступен" + exit 1 + fi + if ! login=$(gh api user -q .login 2>/dev/null); then + echo "::error::HP_PROCESS_TOKEN не аутентифицируется — истёк или отозван. Обновить: Settings -> Secrets and variables -> Actions -> HP_PROCESS_TOKEN" + exit 1 + fi + echo "токен жив, действует от: $login" # Окружение готовит workflow, а не модель своими ходами. Раньше промпт # велел ревьюеру самому выполнить `npm ci`: минуты уходили на установку без @@ -154,8 +174,15 @@ jobs: env: NUM: ${{ github.event.issue.number }} run: | - branch=$(git ls-remote --heads origin "issue/${NUM}-*" \ - | head -1 | sed 's|.*refs/heads/||') + # Свежая по последнему коммиту, а не первая по алфавиту: на #150 рядом + # жили ветка ТЗ и ветка реализации, и head -1 выбрал устаревшую. + git fetch -q origin "+refs/heads/issue/${NUM}-*:refs/remotes/origin/issue/${NUM}-*" || true + branches=$(git for-each-ref --sort=-committerdate \ + --format='%(refname:lstrip=3)' "refs/remotes/origin/issue/${NUM}-*") + branch=$(printf '%s\n' "$branches" | head -1) + if [ "$(printf '%s\n' "$branches" | grep -c .)" -gt 1 ]; then + echo "::warning::веток issue/${NUM}-* несколько ($(echo $branches | tr '\n' ' ')) — выбрана свежая по коммиту: $branch. Устаревшую следует удалить." + fi if [ -n "$branch" ]; then git checkout -q "origin/$branch" echo "материал ревью: ветка $branch, $(git rev-parse --short HEAD)" From ba32234b526276df798e3816dba2e311f1335b67 Mon Sep 17 00:00:00 2001 From: Matysh Date: Tue, 18 Aug 2026 17:54:51 +0300 Subject: [PATCH 3/6] fix: fail loudly when a review verdict has no document On #150 both spec-review verdicts survived only as issue comments: the publish step found nothing staged, printed a warning, and exited zero, so the label moved and the missing artifact went unnoticed until the next review caught it (#171). A verdict without a document in docs/reviews/ now fails the run before the label step, preserving the invariant that an unchanged label means a failed run. An empty working copy alone is not a failure: the reviewer occasionally commits the document itself through its app token, bypassing this step (CODE-REVIEW-150-r1, committer GitHub), so the branch is checked first. A postcondition verifies the exact expected filename reached the branch, and the rebase-conflict path no longer exits zero either. Issue: #171 User-Visible: no --- .github/workflows/process.yml | 37 ++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index 97de391d..1589ed4d 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -330,6 +330,8 @@ jobs: TOKEN: ${{ secrets.HP_PROCESS_TOKEN }} BRANCH: ${{ steps.branch.outputs.name }} NUM: ${{ github.event.issue.number }} + STAGE: ${{ needs.guard.outputs.stage }} + CYCLE: ${{ needs.guard.outputs.cycle }} run: | # Ветки задачи может не быть: у задач, размеченных до появления # конвейера, ТЗ лежит прямо в dev. Раньше шаг в этом случае молча @@ -340,12 +342,28 @@ jobs: if [ -z "$BRANCH" ]; then echo "::warning::ветки задачи нет — документ ревью ляжет в dev" fi + marker=CODE-REVIEW + if [ "$STAGE" = "spec" ]; then marker=SPEC-REVIEW; fi + doc="docs/reviews/${marker}-${NUM}-r${CYCLE}.md" git checkout -- . 2>/dev/null || true git clean -fd -e docs/reviews -e node_modules >/dev/null 2>&1 || true git add docs/reviews 2>/dev/null || true if git diff --cached --quiet; then - echo "::warning::документ ревью не создан" - exit 0 + # Пустая рабочая копия — ещё не провал: ревьюер иногда коммитит + # документ сам, своим app-токеном мимо этого шага (CODE-REVIEW-150-r1, + # коммиттер GitHub). Провал — когда файла нет и на ветке. + git fetch -q origin "$target" + if git cat-file -e "origin/$target:$doc" 2>/dev/null; then + echo "документ уже опубликован ревьюером: $doc" + exit 0 + fi + # Ревью без артефакта запрещено (PROCESS.md §2.4/§10.4/§12). Раньше + # здесь стоял warning с exit 0: на #150 оба вердикта ревью ТЗ + # остались только комментариями, метки переставились, и пропажу + # заметило лишь следующее ревью — issue #171. Падение ДО шага с + # меткой сохраняет инвариант «метка не сменилась = прогон упал». + echo "::error::вердикт есть, а документа $doc нет ни в рабочей копии, ни в $target — ревью без артефакта (#171)" + exit 1 fi git -c user.name="claude[bot]" \ -c user.email="209825114+claude[bot]@users.noreply.github.com" \ @@ -365,13 +383,22 @@ jobs: -c user.email="209825114+claude[bot]@users.noreply.github.com" \ rebase "origin/$target"; then git rebase --abort || true - echo "::error::документ ревью не удалось опубликовать в $target: конфликт" - exit 0 + # Тоже вердикт без артефакта: раньше exit 0 переставил бы метку. + echo "::error::документ ревью не удалось опубликовать в $target: конфликт (#171)" + exit 1 fi git push -q "https://x-access-token:$TOKEN@github.com/${{ github.repository }}" \ "HEAD:$target" fi - echo "документ опубликован в $target" + # Постусловие: до ветки дошёл именно ожидаемый файл. Коммит с + # документом, названным не по формату, — тот же вердикт без + # артефакта, только дороже в обнаружении. + git fetch -q origin "$target" + if ! git cat-file -e "origin/$target:$doc" 2>/dev/null; then + echo "::error::коммит в $target опубликован, но ожидаемого $doc в нём нет — файл назван не по формату (#171)" + exit 1 + fi + echo "документ опубликован в $target: $doc" - name: Решение по вердикту id: decide From 91f2c235393f30d2cf00d0683ff80554e7f5713c Mon Sep 17 00:00:00 2001 From: Matysh Date: Tue, 18 Aug 2026 19:53:15 +0300 Subject: [PATCH 4/6] fix: install Chromium without --with-deps in the review pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a Playwright cache miss the flag pulled Chromium's system libraries through apt, spending minutes of the 45-minute review budget on packages the ubuntu-latest image already ships — and the runner's retries against the unreachable azure mirror made the step look hung on a live run. If the image ever drops a required library, Chromium fails to launch with a clear missing-libraries error; that is the moment to bring the flag back. validate.yml keeps the flag deliberately: it is the prerelease gate, where predictability is worth more than minutes. Issue: #175 User-Visible: no --- .github/workflows/process.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index 1589ed4d..9bb8bb30 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -208,7 +208,12 @@ jobs: - name: Установить Chromium if: steps.pw.outputs.cache-hit != 'true' - run: npx playwright install --with-deps chromium + # Без --with-deps: системные библиотеки Chromium предустановлены в + # образе ubuntu-latest, а apt при промахе кэша съедал минуты из бюджета + # ревью и подолгу перебирал недоступное azure-зеркало (#175). Если + # библиотека когда-нибудь пропадёт из образа, Chromium не запустится с + # внятной ошибкой — тогда флаг вернуть. + run: npx playwright install chromium - name: Review id: review From c1dde9a0cfcf47337760f1ac4eef5b2a3be6057b Mon Sep 17 00:00:00 2001 From: Matysh Date: Wed, 19 Aug 2026 13:16:30 +0300 Subject: [PATCH 5/6] docs: fix in-scope Medium findings inside the current issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filing and servicing a separate issue costs far more than fixing a small problem in place — the owner's call of 2026-08-19 (#202). A Medium finding inside the task's scope no longer becomes its own issue: with no High findings the verdict is yellow, the author fixes it and the fix passes another review cycle. Only an out-of-scope Medium is still filed separately, because foreign scope is never patched from a task branch. Applied to the canon (PROCESS.md), the reviewer prompt in process.yml and AGENTS.md; the verdict format now writes "Medium: N -> in-task | #NN". Issue: #202 User-Visible: no --- .github/workflows/process.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index 9bb8bb30..831de739 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -299,16 +299,20 @@ jobs: Ты НЕ правишь ни ТЗ, ни продуктовый код. Только оцениваешь. - Серьёзность: High блокирует; Medium обязан стать отдельным issue; - Low либо правится, либо снимается с записью. Жёлтый вердикт - допустим при полностью выполненных AC, если изменение не решает - заявленный сценарий или ухудшает смежный. Продуктовое рассуждение - расширяет вопросы, но не отменяет AC и не даёт права менять скоуп. + Серьёзность: High блокирует; Medium В СКОУПЕ задачи чинится в ней + же — без High это жёлтый вердикт и возврат автору, отдельный issue + НЕ заводится (решение владельца 2026-08-19, #202: заведение и + обслуживание issue дороже правки на месте); Low либо правится, + либо снимается с записью. Жёлтый вердикт допустим и при полностью + выполненных AC, если изменение не решает заявленный сценарий или + ухудшает смежный. Продуктовое рассуждение расширяет вопросы, но не + отменяет AC и не даёт права менять скоуп. - Каждую Medium-находку заведи отдельным issue со ссылкой на - #${{ github.event.issue.number }} и метками: тип, приоритет, - S1-new. «Оставили в тексте ревью» закрытием не считается и прямо - запрещено §12. + Только Medium-находку ВНЕ скоупа задачи (попутный дефект соседнего + поведения, который в этой ветке чинить нельзя) заведи отдельным + issue со ссылкой на #${{ github.event.issue.number }} и метками: + тип, приоритет, S1-new. «Оставили в тексте ревью» закрытием не + считается и прямо запрещено §12. Напиши полный документ ревью в файл docs/reviews/-REVIEW-${{ github.event.issue.number }}-r${{ needs.guard.outputs.cycle }}.md @@ -319,7 +323,8 @@ jobs: Затем оставь в issue краткий комментарий: вердикт, ключевые находки и ссылка на документ. Первой строкой — вердикт в формате §7.2: - `Вердикт: зелёный/жёлтый/красный · цикл r${{ needs.guard.outputs.cycle }}/${{ needs.guard.outputs.limit }} · High: N · Medium: N → #…` + `Вердикт: зелёный/жёлтый/красный · цикл r${{ needs.guard.outputs.cycle }}/${{ needs.guard.outputs.limit }} · High: N · Medium: N → в задаче | #…` + («→ #…» — только у Medium вне скоупа; находки в скоупе возвращаются автору жёлтым) Затем верни JSON по схеме. Это последнее действие и оно обязательно: без него метка не переставится и конвейер встанет. From cbaa7b0cb9066aa399503368a06c7ea032a79feb Mon Sep 17 00:00:00 2001 From: Matysh Date: Thu, 20 Aug 2026 11:29:45 +0300 Subject: [PATCH 6/6] docs: scope a repeat review round to the delta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reviewer prompt was identical for every round, and the canon said nothing about the scope of a repeat pass, so r2 re-derived the product framing and re-checked acceptance criteria the fix never touched: the r2 pass on #150 cost a full pipeline run over one line in a test fixture. From the second cycle on, the subject is the delta against the SHA the previous verdict was given on: each earlier finding must be shown closed by a line of code or text, only the criteria the delta can reach are re-verified, and whatever is carried over is listed with the round and SHA it came from. Cheap gates still run every round. The scope shrinks, the strictness does not. A fix can break a criterion an earlier round accepted — that is how regression #102 happened — so the boundary is the findings plus everything the delta can reach, and a non-local delta (a rebase onto a moved dev, a behaviour contract change, a new subsystem) still gets the full pass. Issue: #214 User-Visible: no --- .github/workflows/process.yml | 40 +++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index 831de739..a080a149 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -230,6 +230,38 @@ jobs: Этап: ${{ needs.guard.outputs.stage }} spec — ревью ТЗ (PROCESS.md §2.4) code — код-ревью (PROCESS.md §2.7) + Цикл: r${{ needs.guard.outputs.cycle }} + + **Если цикл не первый — объём разбора по дельте, а не заново** + (PROCESS.md §2.9, issue #214). Раньше промпт был одинаковым для + всех раундов, и повторный цикл заново выводил продуктовую рамку и + перепроверял AC, которых правка не касалась: r2 по #150 стоил + полного прогона ради одной строки в тестовой фикстуре. + + Порядок для r2 и дальше: + 1. найди вердикт предыдущего раунда в комментариях issue и SHA, + на котором он получен. SHA в вердикте не назван — это находка; + 2. объяви дельту: `git diff <тот SHA>..HEAD` для кода, дифф файла + ТЗ или тела issue для spec. Дельта — предмет этого раунда; + 3. по каждой находке предыдущего раунда покажи, чем именно она + закрыта: строка кода или текста, а не заявление автора; + 4. заново проверяй только те AC, чьё доказательство дельта + задевает. Остальные наследуй; + 5. в документе обязателен раздел «Унаследовано из r»: что + принято без повторной проверки, со ссылкой на документ того + раунда и SHA, на котором вывод получен. Без этого перечня + сокращение — молчаливое доверие, а такой тихий успех уже + дважды стоил дня (#171, #207). + + Разбор остаётся ПОЛНЫМ, если дельта не локальна: ребейз на ушедший + вперёд dev (после ребейза это другой код, §7.2), смена контракта + поведения, задета новая подсистема, либо объём дельты сопоставим с + исходной задачей. Сомневаешься — разбирай полностью и скажи почему. + + Сокращается объём РАЗБОРА, а не строгость: правка по замечанию + способна сломать AC, который предыдущий раунд признал выполненным — + так появилась регрессия #102. Поэтому граница не «только находки», а + «находки плюс всё, до чего дотягивается дельта». Прочитай в этом порядке, прежде чем судить: 1. docs/SCOPE.md — зачем продукт существует и для кого. Он @@ -273,7 +305,8 @@ jobs: правке — не тщательность, а потеря времени: полные наборы это предрелизный гейт (PROCESS.md §8), а не гейт ревью. - Всегда, они дешёвые: + Всегда, они дешёвые, и в повторном раунде тоже: код изменился, + а стоят они минуты: `npx tsc --noEmit`, `npm test`, `npm run build` со сверкой трёх копий бандла. @@ -318,7 +351,10 @@ jobs: docs/reviews/-REVIEW-${{ github.event.issue.number }}-r${{ needs.guard.outputs.cycle }}.md (SPEC для этапа spec, CODE для code): скоуп, как проверялось, находки с воспроизведением, что проверено и корректно, чего не - проверял. Каталог docs/reviews/ создай, если его нет. Больше не + проверял. Для r2 и дальше добавь два раздела: «Закрытие раунда + r» — таблица «находка | чем закрыта | где это видно», и + «Унаследовано из r» — что принято без повторной проверки, с + документом и SHA. Каталог docs/reviews/ создай, если его нет. Больше не пиши ничего: любой файл вне docs/reviews/ опубликован не будет. Затем оставь в issue краткий комментарий: вердикт, ключевые находки