mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
docs(hygiene): сократить вход агента, у правила — один дом (#680)
Волна 3 эпика #674. AGENTS.md 650 → 187 строк: карта пакета, маршрут чтения, правило №1, классы и треки одной строкой со ссылками, трейлеры, рабочие деревья, хендофф и ожидание вердикта; пересказы PROCESS.md — ссылками на разделы. Неверный список «Gate jobs» снят (списки jobs не копируются в прозу, шапка PROCESS.md). Правила, жившие только в AGENTS, получили дом: жёлтый вердикт при выполненных AC — PROCESS §2.7; свежесть бандла, съёмка только в Linux (#455, HP_ALLOW_FOREIGN_CAPTURE) и смоки из AC до S7 (#151) — TESTING.md; причуда демо-стенда и среда-зависимый smoke_opening_measure — DEVELOPMENT › Smoke tests; отказ публикации без `Release:` и при несвежем отпечатке бандла, отмена Validate новым пушем, кандидат беты не promotion-only, fail-closed реестра Labs — DEVELOPMENT; предупреждение и ошибка свежести скриншотов — CONTRIBUTING. PROCESS.md: §13 (внедрение с открытым ⏳), §14 (блок со ссылкой на несуществующий docs/PROCESS.md) и §7.3 (история) удалены. Ссылки «§7.2» на правило полного разбора после ребейза ведут в §2.10, на сверку SHA перед выводом — в §2.7; то же в сообщениях scripts/branch-state.mjs, merge-candidate.mjs, review-doc-guard.mjs, pre-push-gate.mjs, в промпте _process.yml и TESTING.md. Число `any` в прозе → `node scripts/no-new-any.mjs --total` (новый режим, юнит-тест; было «1034 в 49 файлах», сейчас 862 в 52), дата-число замороженного списка якорей монолита снято. Устаревшая команда пересъёмки скриншотов в §8 заменена ссылкой на действующий путь. STATUS.md 113 → 61 строка: сгенерированный снимок, текущий цикл и девять строк решений; Workflow, CI, Toolchain, Tests, Scope, open items и политика документации — ссылками (PROCESS §2.6, DEVELOPMENT › Release, TESTING); локали en/ru/de/fr; закрытые «coverage, mypy strict» сняты. DEVELOPMENT.md: file-sync и «Reproducible scripts» (прототип) удалены; раздел Release — единственный дом релизной механики: введение, правила тела стабильного релиза (#328, release:notes), шаг continuity:screencast, источники версии по release-contract. CONTRIBUTING: ссылка на Release вместо пересказа, замеры клона без чисел. TESTING: any-гейт — ссылкой на PROCESS §8. entry-cost: автор 11 125 → 5 407 слов, ревьюер 8 464 → 4 285. Issue: #680 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:
@@ -487,7 +487,7 @@ jobs:
|
|||||||
git fetch -q origin "+refs/heads/$BRANCH:refs/remotes/origin/$BRANCH"
|
git fetch -q origin "+refs/heads/$BRANCH:refs/remotes/origin/$BRANCH"
|
||||||
short_before=$(git rev-parse --short "$before")
|
short_before=$(git rev-parse --short "$before")
|
||||||
short_after=$(git rev-parse --short HEAD)
|
short_after=$(git rev-parse --short HEAD)
|
||||||
echo "note=Ветка приведена к dev конвейером до ревью: поверх легло $behind коммит(ов) dev, $short_before -> $short_after. После ребейза это другой код (§7.2) — разбор полный, а не по дельте." >> "$GITHUB_OUTPUT"
|
echo "note=Ветка приведена к dev конвейером до ревью: поверх легло $behind коммит(ов) dev, $short_before -> $short_after. После ребейза это другой код (§2.10) — разбор полный, а не по дельте." >> "$GITHUB_OUTPUT"
|
||||||
echo "ветка $BRANCH приведена к dev: $short_before -> $short_after"
|
echo "ветка $BRANCH приведена к dev: $short_before -> $short_after"
|
||||||
|
|
||||||
# Материал ревью — конкретный SHA (#312). Вердикт применим только к
|
# Материал ревью — конкретный SHA (#312). Вердикт применим только к
|
||||||
@@ -1015,7 +1015,8 @@ jobs:
|
|||||||
файла в материале нет — читай PROCESS.md §2.4, §2.7, §2.10,
|
файла в материале нет — читай PROCESS.md §2.4, §2.7, §2.10,
|
||||||
§4, §7.2, §8, §12. Задача правит сам конвейер, гейты или
|
§4, §7.2, §8, §12. Задача правит сам конвейер, гейты или
|
||||||
процесс — PROCESS.md целиком, §10 в первую очередь.
|
процесс — PROCESS.md целиком, §10 в первую очередь.
|
||||||
3. AGENTS.md — классы изменений, трейлеры, гейты, формат вердикта.
|
3. AGENTS.md — карта пакета, правило №1, классы изменений, треки,
|
||||||
|
трейлеры и ожидание вердикта; сами правила — по его ссылкам.
|
||||||
4. Тело issue #${{ github.event.issue.number }} и все комментарии.
|
4. Тело issue #${{ github.event.issue.number }} и все комментарии.
|
||||||
5. Если меняется видимое поведение — docs/USER-GUIDE.ru.md:
|
5. Если меняется видимое поведение — docs/USER-GUIDE.ru.md:
|
||||||
терминология интерфейса берётся оттуда, а не изобретается.
|
терминология интерфейса берётся оттуда, а не изобретается.
|
||||||
@@ -1538,7 +1539,7 @@ jobs:
|
|||||||
# Ревью идёт десятки минут, а dev за это время двигается (28 августа —
|
# Ревью идёт десятки минут, а dev за это время двигается (28 августа —
|
||||||
# четыре раза за день). Вердикт при этом вынесен по дереву, которое уже не
|
# четыре раза за день). Вердикт при этом вынесен по дереву, которое уже не
|
||||||
# совпадает с вершиной линии, и слияние приведёт ветку к dev — то есть в
|
# совпадает с вершиной линии, и слияние приведёт ветку к dev — то есть в
|
||||||
# dev уедет код, отличный от прочитанного (§7.2). Молчать об этом нельзя,
|
# dev уедет код, отличный от прочитанного (§2.10). Молчать об этом нельзя,
|
||||||
# но и шуметь на каждом прогоне ни к чему: строка появляется только когда
|
# но и шуметь на каждом прогоне ни к чему: строка появляется только когда
|
||||||
# dev действительно ушёл и вердикт зелёный, то есть слияние вот-вот
|
# dev действительно ушёл и вердикт зелёный, то есть слияние вот-вот
|
||||||
# случится (#364).
|
# случится (#364).
|
||||||
@@ -1557,7 +1558,7 @@ jobs:
|
|||||||
if [ "$moved" -eq 0 ] || [ "$GREEN" != "true" ]; then exit 0; fi
|
if [ "$moved" -eq 0 ] || [ "$GREEN" != "true" ]; then exit 0; fi
|
||||||
short=$(git rev-parse --short "$MATERIAL")
|
short=$(git rev-parse --short "$MATERIAL")
|
||||||
gh issue comment "$NUM" --repo "${{ github.repository }}" --body \
|
gh issue comment "$NUM" --repo "${{ github.repository }}" --body \
|
||||||
"Пока шло ревью, \`dev\` продвинулся на $moved коммит(ов). Материал ревью — \`$short\`. Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии: слияние приведёт ветку к dev, и это другой код (§7.2)."
|
"Пока шло ревью, \`dev\` продвинулся на $moved коммит(ов). Материал ревью — \`$short\`. Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии: слияние приведёт ветку к dev, и это другой код (§2.10)."
|
||||||
|
|
||||||
# S8-merged утверждает, что код в dev. Значит слияние обязано произойти
|
# S8-merged утверждает, что код в dev. Значит слияние обязано произойти
|
||||||
# ДО метки, иначе она врёт в промежутке.
|
# ДО метки, иначе она врёт в промежутке.
|
||||||
|
|||||||
@@ -4,23 +4,26 @@ House Plan is one HACS package with two parts plus a demo harness:
|
|||||||
|
|
||||||
- **Lovelace card** (`src/`, TypeScript + Lit) — the primary product, bundled to
|
- **Lovelace card** (`src/`, TypeScript + Lit) — the primary product, bundled to
|
||||||
the entry, manifest and hashed chunks under `dist/`.
|
the entry, manifest and hashed chunks under `dist/`.
|
||||||
- **Storage integration** (`custom_components/houseplan/`, Python) — the Home Assistant backend.
|
- **Storage integration** (`custom_components/houseplan/`, Python) — the Home
|
||||||
- **Demo harness** (`demo/`) — a self-contained Playwright page (`demo/srv/demo.html`) that renders the card against a fake `hass`, used for screenshots and the `smoke_*.mjs` end-to-end suite. The synthetic home is fully fictional (no real home data in public materials); the launcher is `demo/serve.mjs`, the stand copy of the bundle comes from `npm run bundle:sync`, golden scenes live in `demo/golden/` (its README), performance smokes in `demo/performance/`, the guard suite in `demo/guard/` and the live stand seed in `demo/stand/`.
|
Assistant backend.
|
||||||
|
- **Demo harness** (`demo/`) — a Playwright page (`demo/srv/demo.html`) that
|
||||||
|
renders the card against a fake `hass` for screenshots and the `smoke_*.mjs`
|
||||||
|
suite. The home is fully synthetic. Launcher `demo/serve.mjs`; golden scenes
|
||||||
|
`demo/golden/`, performance `demo/performance/`, guard suite `demo/guard/`,
|
||||||
|
live stand seed `demo/stand/` — each with its own README.
|
||||||
|
|
||||||
|
This file is the map and the few rules every session needs before its first
|
||||||
|
command. `PROCESS.md` is the only complete canon and wins any disagreement;
|
||||||
|
the sections below link to it instead of retelling it.
|
||||||
|
|
||||||
## Read this first
|
## Read this first
|
||||||
|
|
||||||
**`docs/SCOPE.md` before anything else.** It was fixed with the owner and states
|
**`docs/SCOPE.md` before anything else.** It was fixed with the owner and states
|
||||||
its own authority: features are built, improved and accepted **only** if they
|
its own authority: features are built, improved and accepted **only** if they
|
||||||
serve a job listed there. It carries the mission, the three personas, the core
|
serve a job listed there. Its central consequence: **View mode is the product
|
||||||
user jobs and the out-of-scope list.
|
for two of the three personas.** Editors are admin-only tools and must never
|
||||||
|
leak interactions into View. For work that changes visible behaviour, also read
|
||||||
Its central consequence: **View mode is the product for two of the three
|
`docs/USER-GUIDE.ru.md` — interface wording comes from there and is not invented.
|
||||||
personas.** Editors are admin-only tools and must never leak interactions into
|
|
||||||
View.
|
|
||||||
|
|
||||||
For work that changes visible behaviour, also read `docs/USER-GUIDE.ru.md` —
|
|
||||||
interface wording comes from there and is not invented, or the UI starts speaking
|
|
||||||
developer.
|
|
||||||
|
|
||||||
**Reading order by role** (#634). `node scripts/entry-cost.mjs` measures each
|
**Reading order by role** (#634). `node scripts/entry-cost.mjs` measures each
|
||||||
route and `test/entry-cost.test.mjs` keeps this list equal to its routes:
|
route and `test/entry-cost.test.mjs` keeps this list equal to its routes:
|
||||||
@@ -32,64 +35,18 @@ route and `test/entry-cost.test.mjs` keeps this list equal to its routes:
|
|||||||
- changing the pipeline, the gates or the process itself: `docs/SCOPE.md` →
|
- changing the pipeline, the gates or the process itself: `docs/SCOPE.md` →
|
||||||
`AGENTS.md` → `PROCESS.md` → `docs/STATUS.md`.
|
`AGENTS.md` → `PROCESS.md` → `docs/STATUS.md`.
|
||||||
|
|
||||||
The two digests quote and link `PROCESS.md` section by section; it stays the
|
The two digests quote and link `PROCESS.md` section by section; open the linked
|
||||||
only complete canon and wins any disagreement, so open the linked section
|
section whenever a digest line governs your current step. For non-trivial
|
||||||
whenever a digest line governs your current step. For non-trivial changes add
|
changes add `docs/ARCHITECTURE.md` plus the canonical document of the subsystem
|
||||||
`docs/ARCHITECTURE.md` plus the canonical document of the subsystem you touch
|
you touch (one list, the same one the reviewer prompt in `_process.yml` reads):
|
||||||
(one list, the same one the reviewer prompt in `_process.yml` reads):
|
|
||||||
`SUN.md`, `LIGHT.md`, `CANVAS.md`, `WALL-THICKNESS.md`, `UX-MODES.md`,
|
`SUN.md`, `LIGHT.md`, `CANVAS.md`, `WALL-THICKNESS.md`, `UX-MODES.md`,
|
||||||
`CONFIG-COMPATIBILITY.md`, `TOUCH-SUPPORT.md`, `ISOMETRIC.md`, `VACUUM.md`,
|
`CONFIG-COMPATIBILITY.md`, `TOUCH-SUPPORT.md`, `ISOMETRIC.md`, `VACUUM.md`,
|
||||||
`DECOR-EDITOR.md`, `DEVICE-PRESENTATION.md`, `FILTERING.md`, `STAIRS.md`,
|
`DECOR-EDITOR.md`, `DEVICE-PRESENTATION.md`, `FILTERING.md`, `STAIRS.md`,
|
||||||
`RADAR.md`, `PDF-EXPORT.md`, `STYLING-HOOKS.md`.
|
`RADAR.md`, `PDF-EXPORT.md`, `STYLING-HOOKS.md`.
|
||||||
|
|
||||||
Standard commands live in `package.json` scripts, `CONTRIBUTING.md` and
|
Where the rest lives: commands — `package.json` scripts, `CONTRIBUTING.md`,
|
||||||
`docs/DEVELOPMENT.md`.
|
`docs/DEVELOPMENT.md` (toolchain, build, release — its Release section is the
|
||||||
|
only home of release mechanics); tests and gates — `docs/TESTING.md`.
|
||||||
## Canonical backlog and status
|
|
||||||
|
|
||||||
[GitHub Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical
|
|
||||||
task records: problem, scope, acceptance criteria and discussion.
|
|
||||||
|
|
||||||
**Status lives in labels:** `S1-new`, `S2-analysis`, `S3-spec`, `S4-spec-review`,
|
|
||||||
`S5-ready`, `S6-in-progress`, `S7-code-review`, `S8-merged`, plus `blocked` on top
|
|
||||||
of a status and `rejected` on a closed issue. A product issue in flight carries
|
|
||||||
exactly one `S*` label. An infrastructure-only issue is the deliberate exception:
|
|
||||||
it may carry no `S*` label while being implemented, enters the common flow at
|
|
||||||
`S7-code-review`, and from then on carries exactly one. Labels are the whole of
|
|
||||||
it: GitHub Projects is no longer used.
|
|
||||||
|
|
||||||
**The light track is the default, not a shortcut** (owner's decision 2026-08-27,
|
|
||||||
issue #338). `small` — the spec lives in the issue body and its review is a
|
|
||||||
comment. Analysis names the `small` criterion the task *fails* when it takes the
|
|
||||||
full track; "ordinary track" without a named criterion is not a justification.
|
|
||||||
The threshold itself did not move — only which side carries the proof. The full
|
|
||||||
track stays what it was for geometry, config migrations and public contracts,
|
|
||||||
where a criterion is broken plainly and saying which one is easy.
|
|
||||||
|
|
||||||
`trivial` — the short track: no spec stage at all, `S2-analysis` straight to
|
|
||||||
`S5-ready`, with the AC written into the issue body first. `trivial` requires a bug confined to one surface with no new UX
|
|
||||||
contract, no migration, no i18n, no perf or touch impact, at most three checkable
|
|
||||||
AC, **and expected behaviour already on record** — nothing left to decide. Code
|
|
||||||
review is never skipped on either track: it checks scope, risks and the evidence
|
|
||||||
from executed tests, but does not stand in for executing them.
|
|
||||||
`PROCESS.md` §5 and §5.1 hold the criteria.
|
|
||||||
|
|
||||||
An issue filed by an outsider is worked exactly like one of the owner's own, once
|
|
||||||
the owner has decided to take it. For product work the check sits **at the
|
|
||||||
entrance**, not on every step: the first status label admits it to the flow. For
|
|
||||||
infrastructure work an explicit assignment by the owner is the entrance, and the
|
|
||||||
issue may remain without an `S*` label until its first `S7-code-review`. In either
|
|
||||||
case, **who filed it stops mattering** once the owner has admitted it.
|
|
||||||
|
|
||||||
Applying that first label *is* the owner's explicit decision, and the platform
|
|
||||||
already guarantees it — only someone with write access can label. The earlier rule
|
|
||||||
made outside reports be refiled as the owner's own issues, which turned out to be
|
|
||||||
work for nothing: on #123 the spec was already written by the time the guard
|
|
||||||
refused.
|
|
||||||
|
|
||||||
Specs, audits and ADRs may live under `docs/`, but must link to their issue and
|
|
||||||
must not become a parallel task list. When repository documentation disagrees with
|
|
||||||
Issues, the issue wins.
|
|
||||||
|
|
||||||
## Rule #1
|
## Rule #1
|
||||||
|
|
||||||
@@ -106,183 +63,100 @@ The label must be one of `S5-ready`, `S6-in-progress`, `S7-code-review`. Anythin
|
|||||||
else — refuse and say why. "Issue #83 is in `S2-analysis`, code is off limits.
|
else — refuse and say why. "Issue #83 is in `S2-analysis`, code is off limits.
|
||||||
Start with the spec?" is the correct answer, not a smaller patch.
|
Start with the spec?" is the correct answer, not a smaller patch.
|
||||||
|
|
||||||
## Change classes
|
GitHub Issues are the canonical task records and the **labels** are the status
|
||||||
|
(`PROCESS.md` §9); when repository documentation disagrees with an issue, the
|
||||||
|
issue wins. Change classes (`PROCESS.md` §1): **A** product (`src/**`,
|
||||||
|
integration Python, manifests, i18n), **B** gates and tooling (tests, `demo/**`,
|
||||||
|
`scripts/**`, `.github/**`, build and package configuration), **C**
|
||||||
|
documentation, **D** generated (bundle, golden baselines) — D beats A where paths
|
||||||
|
overlap. The committed bundle changes only in a commit with a `Release:` trailer
|
||||||
|
(#657); an ordinary task restores it with `npm run bundle:clean` before
|
||||||
|
committing.
|
||||||
|
|
||||||
| Class | Paths | Issue required |
|
**Tracks** (`PROCESS.md` §5, §5.1): `small` is the default — the spec lives in the
|
||||||
|---|---|---|
|
issue body and its review is a comment; taking the full track means naming the
|
||||||
| **A — product** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, i18n, `custom_components/**/translations/**` | yes |
|
`small` criterion the task fails. `trivial` skips the spec stage for a bug whose
|
||||||
| **B — gates and tooling** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, `.github/workflows/**`, `rollup.config.mjs`, `tsconfig*.json` | yes; may reuse the issue it covers |
|
expected behaviour is already on record. An **infrastructure** task — not a single
|
||||||
| **C — documentation** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md` | not if it is part of its issue's DoD |
|
class A file — skips analysis and spec and enters at `S7-code-review`
|
||||||
| **D — generated** | `dist/**`, `custom_components/houseplan/frontend/**`, `demo/golden/baselines/**` | never changes on its own. The stand copy `demo/srv/assets/**` is no longer committed (#255): build it with `npm run bundle:sync`. The bundle (`dist/**`, `custom_components/houseplan/frontend/**`) changes only in a commit with a `Release:` trailer — the beta/release candidate, `npm run bundle:release` (#657); an ordinary task leaves it behind the sources and restores the build with `npm run bundle:clean` before committing |
|
(`PROCESS.md` §1). Code review is never skipped on any track: it checks scope,
|
||||||
|
risks and the evidence from executed tests, but does not replace executing them.
|
||||||
|
|
||||||
The table above is a summary; `PROCESS.md` §1 is the authority and now covers the
|
## Specs
|
||||||
configuration files this one omits — `package.json`, `package-lock.json`,
|
|
||||||
`pytest.ini`, `.gitignore`, `.gitattributes`, `.githooks/**` and the rest of
|
|
||||||
`.github/**` are class B. Where paths overlap, **D beats A**: the built bundle
|
|
||||||
lives inside `custom_components/houseplan/frontend/` and would otherwise read as
|
|
||||||
product source.
|
|
||||||
|
|
||||||
## Commits
|
The spec lives in the **issue body**, under a `## ТЗ` heading (owner decision
|
||||||
|
2026-09-10, #517); `docs/specs/` is an archive of specs written before that date
|
||||||
|
and takes no new files. Required sections and the rule for questions are
|
||||||
|
`PROCESS.md` §7.1: only **product** ambiguity goes to the owner — what a person
|
||||||
|
sees or does, and how much user-visible change belongs in this issue — in one
|
||||||
|
batched comment with a proposed default for each question and `blocked` on top of
|
||||||
|
`S3-spec`. Everything a user cannot observe is yours to decide and record.
|
||||||
|
|
||||||
Hooks install themselves: `package.json` runs `"prepare": "node
|
## Commits and branches
|
||||||
scripts/install-hooks.mjs"`, so `npm ci` sets `core.hooksPath` in every fresh
|
|
||||||
clone. Verify with `git config core.hooksPath` — expect `.githooks`.
|
|
||||||
|
|
||||||
Every non-merge commit carries **terminal** trailers:
|
Hooks install themselves on `npm ci` (`prepare` → `scripts/install-hooks.mjs`);
|
||||||
|
`git config core.hooksPath` must print `.githooks`. Every non-merge commit
|
||||||
|
carries **terminal** trailers:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Issue: #123
|
Issue: #123
|
||||||
User-Visible: yes
|
User-Visible: yes
|
||||||
```
|
```
|
||||||
|
|
||||||
One `Issue:` line per issue if a commit closes several. `User-Visible: no` for
|
One `Issue:` line per issue. `User-Visible: yes` requires edits to **both**
|
||||||
tests, refactors, tooling and documentation that does not change the product.
|
`docs/CHANGELOG.md` and `docs/CHANGELOG.ru.md` in the same commit. A commit
|
||||||
`User-Visible: yes` requires edits to **both** changelogs — `docs/CHANGELOG.md`
|
touching `demo/golden/baselines/**` also needs `Release:` plus exactly one of
|
||||||
and `docs/CHANGELOG.ru.md` — in the same commit.
|
`Baseline-Reviewed: <GitHub run URL>` or `Baseline-Reviewed-Local: sha256:<WSL
|
||||||
|
attestation>` (`PROCESS.md` §10.1). Never invent a review link and never rewrite
|
||||||
|
published history to satisfy trailers.
|
||||||
|
|
||||||
A commit touching `demo/golden/baselines/**` additionally requires:
|
Branch `issue/<NN>-slug`; direct commits to `dev`, no PR (owner's decision); a
|
||||||
|
violation is fixed with a follow-up commit, never a force-push.
|
||||||
|
|
||||||
```text
|
- **Push after every task, not before a beta** — while work sits unpushed there
|
||||||
Release: v1.62.0-beta.9
|
is nothing to review.
|
||||||
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/<run-id>
|
- **Standing permission: push `issue/<NN>-slug` without asking.** The reviewer
|
||||||
```
|
runs in CI and reads only the remote; a task branch publishes nothing to users.
|
||||||
|
Push the material **before** applying the review label.
|
||||||
or, for a complete attested capture made in the repository's WSL/ext4 clone:
|
- **Do not merge into `dev` by hand.** On a green review the pipeline rebases,
|
||||||
|
pushes and only then sets `S8-merged`. A conflicting rebase returns the task to
|
||||||
```text
|
`S6-in-progress` with the verdict intact; `node scripts/rebase-on-dev.mjs`
|
||||||
Release: v1.62.0-beta.9
|
resolves conflicts in generated files only (`PROCESS.md` §2.10), then push and
|
||||||
Baseline-Reviewed-Local: sha256:<wsl-attestation hash>
|
re-apply `S7-code-review`.
|
||||||
```
|
- Everything else needs the owner's explicit command: pushing `main`, tags,
|
||||||
|
publishing betas and releases, closing issues.
|
||||||
Exactly one of the two `Baseline-Reviewed*` trailers is allowed. The local hash
|
|
||||||
must equal `localAttestation.sha256` in the committed baseline index; it does not
|
|
||||||
replace the mandatory full GitHub Validate on the final exact SHA.
|
|
||||||
|
|
||||||
Never invent a review link and never rewrite published history to satisfy
|
|
||||||
trailers. `.githooks/commit-msg` and the `provenance` CI job both run
|
|
||||||
`scripts/validate-commit-provenance.mjs`.
|
|
||||||
|
|
||||||
Branch: `issue/<NN>-slug`. Direct commits to `dev`, no PR — the owner's decision;
|
|
||||||
CI checks after the fact, and a violation is fixed with a follow-up commit, never
|
|
||||||
a force-push.
|
|
||||||
|
|
||||||
**Push after every task, not before a beta.** While work sits unpushed there is
|
|
||||||
nothing to review, and reviewing twenty tasks at once is not review. `dev` may hold
|
|
||||||
unreviewed code while a task is in flight; what matters is its state when the
|
|
||||||
reviewer says it is accepted.
|
|
||||||
|
|
||||||
**Standing permission: push `issue/<NN>-slug` without asking.** The reviewer runs
|
|
||||||
in CI and can only read what is on the remote — an unpushed spec or commit means
|
|
||||||
the review either stalls or judges the wrong tree. Pushing a task branch publishes
|
|
||||||
nothing to users and does not touch the integration branch, so it needs no command.
|
|
||||||
|
|
||||||
**Do not merge into `dev` by hand.** On a green code review the pipeline rebases
|
|
||||||
the task branch onto `dev`, pushes it, and only then sets `S8-merged` — the label
|
|
||||||
asserts the code is in `dev`, so the merge has to happen first or the label lies
|
|
||||||
in between.
|
|
||||||
|
|
||||||
If the rebase conflicts the pipeline says so in the issue and sends the task back
|
|
||||||
to `S6-in-progress`. The verdict still stands: nothing needs reviewing again, the
|
|
||||||
remaining work is the rebase. When the conflict is only in generated files, run
|
|
||||||
`node scripts/rebase-on-dev.mjs` (#479): a conflict in the committed bundle
|
|
||||||
(`dist/**`, `custom_components/houseplan/frontend/**` — possible only for a branch
|
|
||||||
started before #657, which stopped tasks from committing it) takes `dev`'s copy
|
|
||||||
and continues, with no rebuild and no amend; a conflict in `docs/reviews/INDEX.md` is
|
|
||||||
rebuilt from the directory (#643, the same helper the pipeline uses, so the
|
|
||||||
pipeline no longer bounces a task on it); a conflict anywhere else aborts and
|
|
||||||
leaves the tree as it was. Then push the branch and re-apply `S7-code-review`. When the
|
|
||||||
only difference from the reviewed material is the pipeline's own review-document
|
|
||||||
commit, the next run re-applies the green verdict without calling the model
|
|
||||||
(#499); any other change to the tree — a rebase included — gets a full review.
|
|
||||||
|
|
||||||
The pipeline is an idempotent controller (#499): only `S4-spec-review` and
|
|
||||||
`S7-code-review` start it, it reads the issue's *current* labels rather than the
|
|
||||||
event snapshot, and a label removed before the run starts is treated as a
|
|
||||||
withdrawn request. Push the material **before** applying the label — the reviewer
|
|
||||||
is pinned to the SHA the pipeline captured and must not fetch newer commits. The second review run is not a formality — after a rebase onto a
|
|
||||||
moved `dev` this is different code, and accepting it unchecked is how regressions
|
|
||||||
arrive. Cycles are counted per stage, so a code review spends its own budget.
|
|
||||||
|
|
||||||
Everything else still requires the owner's explicit command: pushing `main`,
|
|
||||||
creating tags, publishing betas and releases, closing issues.
|
|
||||||
|
|
||||||
## Working trees (#115)
|
## Working trees (#115)
|
||||||
|
|
||||||
One checkout, one `HEAD`: two agents sharing a directory inherit each other's
|
One checkout, one `HEAD`: two agents sharing a directory inherit each other's
|
||||||
branch, and twice in one hour a commit landed on someone else's task branch that
|
branch. `houseplan-card-src/houseplan-card` is the author's tree — task branches
|
||||||
way. The layout is therefore fixed:
|
live there, and unfamiliar local changes belong to the author or the owner,
|
||||||
|
never reset or clean them away. `houseplan-card-src/hp-dev` is the owner's
|
||||||
|
worktree, permanently on `dev`. The reviewer owns no tree: it runs in CI on a
|
||||||
|
fresh checkout. A worktree works only on the machine that created it — its
|
||||||
|
`.git` file records an absolute path in that machine's format.
|
||||||
|
|
||||||
- **`houseplan-card-src/houseplan-card`** — the author's tree. Task branches live
|
## Handoff and the verdict
|
||||||
here; nobody else commits in it. Unfamiliar local changes belong to the author
|
|
||||||
or the owner — never reset or clean them away.
|
|
||||||
- **`houseplan-card-src/hp-dev`** — the owner's worktree, permanently on `dev`. For owner-side operations that must not disturb the
|
|
||||||
author's tree: pushing `dev`, restoring a hook's executable bit, emergencies.
|
|
||||||
- **The reviewer owns no author tree.** It runs in CI on a fresh checkout. The
|
|
||||||
agent implementing an infrastructure task is an ordinary task author and uses
|
|
||||||
the same author-tree rules as product work; task branches must not share a
|
|
||||||
mutable checkout concurrently.
|
|
||||||
|
|
||||||
A worktree is only usable on the machine that created it: the `.git` file records
|
Start a task from its packet: `node scripts/task-packet.mjs --issue NN` (status,
|
||||||
an absolute path in that machine's format. One created from a Linux sandbox is
|
track, what the status permits, the branch against `dev`, the previous verdict
|
||||||
dead on Windows and vice versa — create worktrees on the machine that will use
|
and the unwitnessed AC; it writes nothing). The local gate is
|
||||||
them, which for `hp-dev` means the owner's.
|
`npm run gate:small` (`docs/TESTING.md` › Локальный набор перед пушем); run the
|
||||||
|
smokes named in the AC before `S7-code-review`. **"Verified" without a named
|
||||||
|
command and its result is not evidence.** Comment formats — claim, handoff,
|
||||||
|
verdict — are `PROCESS.md` §7.2.
|
||||||
|
|
||||||
## Agent-neutral workflow
|
**One handoff, one push** (`PROCESS.md` §10.4). Run
|
||||||
|
`node scripts/process-gate.mjs --issues` before pushing; after
|
||||||
|
`S7-code-review` do not push to the branch until the verdict or the return
|
||||||
|
arrives — a push on top of a running review cancels it.
|
||||||
|
|
||||||
No task type is reserved for Codex, Claude or any other named model. **Any agent
|
**Having applied `S4-spec-review` or `S7-code-review`, wait for the result
|
||||||
may take any task**: analysis, spec, product implementation, infrastructure or a
|
instead of ending the session.** The label starts the pipeline by itself. Poll
|
||||||
release explicitly commanded by the owner. Roles describe the current artifact,
|
with `node scripts/wait-verdict.mjs --issue NN [--sha <tip>]` (#496): it watches
|
||||||
not the agent brand. The owner rules on product disputes, closes issues and
|
the label and the pipeline's comments every 90 s, at most 110 times, prints only
|
||||||
commands releases.
|
on a change and exits 0 on a new label, 3 on an event that needs a hand, 4 on
|
||||||
|
timeout. Watch the **label**, not the comment. Do not wait while `blocked` is
|
||||||
Author and reviewer are independent agents/sessions. They need not use different
|
set.
|
||||||
model families, but the reviewer must start without implementation context and
|
|
||||||
must not be the author grading their own work. The reviewer does not edit the
|
|
||||||
material under review.
|
|
||||||
|
|
||||||
**Infrastructure-only work uses an accelerated entry into the common flow.** It
|
|
||||||
is implemented immediately by any agent, without analysis, spec, spec review or
|
|
||||||
the statuses `S1`…`S6`. Once the branch is ready and pushed, apply
|
|
||||||
`S7-code-review`. From there the ordinary controller applies: green review rebases
|
|
||||||
and merges the checked material into `dev` and then sets `S8-merged`; findings or
|
|
||||||
a failed merge return the issue to `S6-in-progress`, and after correction it is
|
|
||||||
submitted to `S7-code-review` again.
|
|
||||||
|
|
||||||
The test for "infrastructure only" is mechanical: **not a single class A file** —
|
|
||||||
nothing under `src/**`, no `custom_components/**/*.py`, no manifests, no i18n. A
|
|
||||||
task that touches class A even once is not infrastructure and takes the full flow;
|
|
||||||
there is no such thing as "mostly infrastructure". The strictness is deliberate:
|
|
||||||
a loose reading would turn this into the route by which product changes skip
|
|
||||||
review.
|
|
||||||
|
|
||||||
What stays mandatory either way: an issue exists, both trailers are on every
|
|
||||||
commit, proportionate local gates are green (normally `typecheck`, `test` and
|
|
||||||
`build`), and any non-obvious decision is written down in the code or the issue
|
|
||||||
rather than kept in someone's head. Infrastructure work skips specification, not
|
|
||||||
code review.
|
|
||||||
|
|
||||||
**Review starts by itself.** Applying `S4-spec-review` or `S7-code-review` fires the
|
|
||||||
pipeline. Deterministic gates, model review and integration have independent
|
|
||||||
55/45/55-minute budgets (#551); typical runs finish well before those ceilings.
|
|
||||||
|
|
||||||
**Having applied one of those labels, wait for the result instead of ending the
|
|
||||||
session.** Reporting "handed over for review" stops a conveyor that could have kept
|
|
||||||
moving on its own. An agent has no clock — it exists only during its own turn — so
|
|
||||||
waiting means polling: every 90 seconds, at most 110 times. A single long sleep hits
|
|
||||||
the command timeout. Do the polling with `node scripts/wait-verdict.mjs --issue NN
|
|
||||||
[--sha <tip>]` (#496): it watches the label, the pipeline's own comments (conflict,
|
|
||||||
cancelled merge, failed run) and optionally Validate on the SHA, prints only when
|
|
||||||
the state changes and exits 0 on a new label, 3 on an event that needs a hand,
|
|
||||||
4 on timeout — the same 90 s × 110 without a model turn per tick. It writes
|
|
||||||
nothing. Pipeline comments older than the latest application of `S4`/`S7` are
|
|
||||||
the baseline, not an outcome of the new round; an outcome from the current round
|
|
||||||
which already exists when the waiter starts is still delivered immediately (#546).
|
|
||||||
Watch the **label**, not the comment: the label is the state,
|
|
||||||
the comment only explains it. Do not wait at all while `blocked` is set — the task
|
|
||||||
is waiting on the owner, not on the reviewer. On exhausting the attempts, stop and
|
|
||||||
tell the owner: a failed run leaves the label where it was, forever.
|
|
||||||
|
|
||||||
What the new label means:
|
|
||||||
|
|
||||||
| Now reads | What happened | What you do |
|
| Now reads | What happened | What you do |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -293,358 +167,21 @@ What the new label means:
|
|||||||
| `review-4` | the cycle limit is spent | stop, the owner decides |
|
| `review-4` | the cycle limit is spent | stop, the owner decides |
|
||||||
|
|
||||||
**After a review run the label always changes.** If it did not, the run itself
|
**After a review run the label always changes.** If it did not, the run itself
|
||||||
failed rather than the work — say so to the owner instead of polling on.
|
failed rather than the work — say so to the owner instead of polling on. The
|
||||||
|
bounded queue reconciler (#555) re-wakes a review whose event was lost; it is a
|
||||||
|
safety net, not permission to stop waiting for the review you started.
|
||||||
|
|
||||||
The repository also has a bounded queue reconciler (#555). It takes one S4/S7
|
A failed pre-release gate (golden, full smokes, performance, HA harness) does not
|
||||||
snapshot every thirty minutes and exits. It may re-apply the same review label
|
send the issue back to review: fix, re-run what failed, record the exact command
|
||||||
only when the matching event was lost or its run ended with a transient
|
and result in the issue (`PROCESS.md` §11.4 — and its limits).
|
||||||
cancellation/timeout before a sealed result existed. It never applies verdicts or
|
|
||||||
merges. Running work, `blocked`, `review-4`, a foreign material/stage/attempt, a
|
|
||||||
guard failure, or an unintegrated sealed model result is left untouched and gets
|
|
||||||
at most one machine-keyed diagnostic. This is a safety net, not permission for an
|
|
||||||
agent to stop waiting for the result of the review it started.
|
|
||||||
|
|
||||||
**A failed pre-release gate does not send the issue back to review.** The
|
|
||||||
implementation loop runs only typecheck, unit and build; golden, browser smokes,
|
|
||||||
performance and the full HA harness run before a beta, which is after the code
|
|
||||||
review has passed and the issue sits in `S8-merged`. Some defects cannot surface
|
|
||||||
any earlier.
|
|
||||||
|
|
||||||
Fix it, re-run what failed, and a green run is enough for the release to continue.
|
|
||||||
The issue stays in `S8-merged`. Record the **exact command and its result** in the
|
|
||||||
issue — "verified" without a command proves nothing. Trailers as usual, and
|
|
||||||
`User-Visible: yes` still means both changelogs in the same commit.
|
|
||||||
|
|
||||||
The exception covers repairing the defect the gate named, not carrying on
|
|
||||||
development under the name of a repair. It goes through the normal flow — a new
|
|
||||||
issue, or back to `S6-in-progress` — if the fix changes a behaviour contract, gives
|
|
||||||
the user something new, reaches a subsystem the task never touched, or is
|
|
||||||
comparable in size to the task itself. And editing the gate so it stops failing is
|
|
||||||
concealment, not repair; the exception is a defect proven to be **in the fixture**,
|
|
||||||
as on #89, where the sun sat at azimuth 180° and the only window faced north, so no
|
|
||||||
ray was ever built.
|
|
||||||
|
|
||||||
Baselines are still accepted only via `npm run golden:accept -- --reviewed` from
|
|
||||||
either a complete Linux CI artefact or a complete attested WSL artefact produced
|
|
||||||
by `npm run golden:wsl:capture`. "So the gate goes green" is not a reason, and
|
|
||||||
the WSL route never replaces the independent full GitHub Validate on the final
|
|
||||||
exact SHA.
|
|
||||||
|
|
||||||
The exchange happens in **issue comments** — there is no local message bus. Verdict
|
|
||||||
format:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Verdict: green/yellow/red · cycle r<N>/4 · High: N · Medium: N → in-task | #… · Document: …
|
|
||||||
```
|
|
||||||
|
|
||||||
High blocks. A Medium finding INSIDE the task's scope is fixed within the task:
|
|
||||||
with no High findings the verdict is yellow, the author fixes it and the fix
|
|
||||||
passes another review cycle — no separate issue (owner's decision 2026-08-19,
|
|
||||||
#202: filing and servicing an issue costs far more than fixing in place). Only
|
|
||||||
a Medium finding OUTSIDE the scope becomes its own issue — foreign scope is
|
|
||||||
never patched from this branch. Low is fixed or waived with a note
|
|
||||||
in the review document. A yellow verdict is legitimate even when every acceptance
|
|
||||||
criterion passes, if the change does not solve the stated scenario or degrades a
|
|
||||||
neighbouring one.
|
|
||||||
|
|
||||||
**Four review cycles** (two on the light track). The counter lives in the document
|
|
||||||
name, `-r1`…`-r4`; the fourth adds the `review-4` label. There is no fifth attempt:
|
|
||||||
the owner splits the task, rejects it, or arbitrates.
|
|
||||||
|
|
||||||
On the light track (`small`: complexity ≤3, one surface, no config migration, no
|
|
||||||
new UX contract, no perf or touch impact — all at once) the spec lives in the issue
|
|
||||||
body and the spec review is a comment. Code review is never skipped. This track is
|
|
||||||
the default: taking the full one means naming the criterion above that the task
|
|
||||||
does not meet.
|
|
||||||
|
|
||||||
## Specs
|
|
||||||
|
|
||||||
The spec lives in the **issue body**, under a `## ТЗ` heading (owner decision
|
|
||||||
2026-09-10, #517); `docs/specs/` is an archive of specs written before that date
|
|
||||||
and takes no new files. Required sections are in `PROCESS.md` §7.1, plus two
|
|
||||||
product ones: which persona meets this, on which surface, at what moment; and what
|
|
||||||
the person sees before and after, in one sentence without implementation terms.
|
|
||||||
Proof that a verdict was passed on a given text is the pipeline's job: it writes
|
|
||||||
the `sha256` of the normalised body into the review document's anchor block, and
|
|
||||||
an edit made after a green spec review reaches the code reviewer as a finding.
|
|
||||||
|
|
||||||
**Ambiguity is asked, not guessed — but only product ambiguity.** A guess written as
|
|
||||||
fact is the worst kind of defect: it passes review because it looks like a decision.
|
|
||||||
|
|
||||||
The owner answers exactly two kinds of question: **what a person sees or does**, and
|
|
||||||
**how much user-visible change belongs in this issue**. Behaviour in a boundary case,
|
|
||||||
which persona wins when two conflict, what counts as acceptable degradation, whether
|
|
||||||
a neighbouring behaviour is in scope here or becomes its own issue.
|
|
||||||
|
|
||||||
Everything a user cannot observe is yours to settle: where state is stored, which
|
|
||||||
module carries the guard, naming, file layout, test strategy, migration mechanics,
|
|
||||||
development policy. Decide it, record it in an explicit "assumed, change freely"
|
|
||||||
block, and let the reviewer challenge it. A technical disagreement between author and
|
|
||||||
reviewer is settled by the verdict, not by the owner; it reaches him only when the
|
|
||||||
cycle limit is exhausted.
|
|
||||||
|
|
||||||
Split a mixed question instead of escalating all of it. "Where does this state live"
|
|
||||||
is technical. "Does it survive a page reload and follow the plan across screens" is
|
|
||||||
product. Ask the second, decide the first.
|
|
||||||
|
|
||||||
Ask in one batched issue comment, each question carrying a proposed default, and put
|
|
||||||
`blocked` on top of `S3-spec` while waiting. A question with a default costs the
|
|
||||||
owner seconds; one without costs him minutes.
|
|
||||||
|
|
||||||
## Gates
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run typecheck
|
|
||||||
npm test
|
|
||||||
npm run build
|
|
||||||
npm run inventory # the only correct way to get test counts
|
|
||||||
```
|
|
||||||
|
|
||||||
Never copy test counts into documents by hand; they go stale in days.
|
|
||||||
|
|
||||||
After building, lay the bundle out for the stand; only a candidate updates the
|
|
||||||
committed copy (#657):
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run bundle:sync # build + dist → demo/srv/assets (#255)
|
|
||||||
npm run bundle:clean # before an ordinary commit: dist back to the committed copy (#657)
|
|
||||||
npm run bundle:release # candidate only: build + dist → custom_components + demo/srv/assets
|
|
||||||
npm run bundle:budget # initial View graph within INITIAL_VIEW_GZIP_BUDGET (scripts/bundle-budget.mjs, #337/#367)
|
|
||||||
```
|
|
||||||
|
|
||||||
CI (`bundle-policy --verify`) checks the fresh build's integrity on every push
|
|
||||||
and compares it byte-for-byte with the committed copy only where the commit
|
|
||||||
changes the bundle or is a candidate. Publishing a beta additionally refuses a
|
|
||||||
committed bundle whose embedded source fingerprint differs from the tree. The
|
|
||||||
dev stand gets the head of `dev` from the Validate artifact via the `dev-build`
|
|
||||||
branch, not from the tree (`demo/stand/README.md`).
|
|
||||||
|
|
||||||
`npm run gate:small` runs the mandatory part of PROCESS §8 in one go (#479,
|
|
||||||
#576): build with typecheck, `no-new-any`, `no-new-private-writes` and `smoke-select` start in parallel;
|
|
||||||
unit tests follow the completed build because their bundle-contract witnesses
|
|
||||||
read the freshly produced `dist`, then the bundle-tree comparison and the
|
|
||||||
bundle budget run. It prints the smokes the
|
|
||||||
diff selects but does not run them by default — `npm run gate:small -- --smokes`
|
|
||||||
(#496) adds the browser phase after the artefact preparation: `bundle-sync`, then
|
|
||||||
the directly matched and registered smokes two at a time; "broad" matches stay
|
|
||||||
the reviewer's call. `golden`, `pytest` and `check-docs --screenshots=strict`
|
|
||||||
remain the author's call by diff and AC.
|
|
||||||
|
|
||||||
**Start a task from its packet** (#496): `node scripts/task-packet.mjs --issue NN`
|
|
||||||
prints one derived view — status and track, what the status permits, the owner's
|
|
||||||
recent decisions, the branch against `dev` and Validate on its tip, the previous
|
|
||||||
verdict with its recorded tree, AC → evidence from the last review document and
|
|
||||||
what is still unwitnessed. It reads GitHub and git and writes nothing; the labels
|
|
||||||
remain the only source of status.
|
|
||||||
|
|
||||||
**Heavy CI gates run on the beta candidate, nightly and on demand — not on every
|
|
||||||
push (#479).** `smoke`, `golden` and `performance_smoke` in Validate are gated
|
|
||||||
on the `heavy` output: true for a head commit carrying a `Release:` trailer, for
|
|
||||||
`workflow_dispatch full=true` (which `nightly.yml` issues on `dev` every night)
|
|
||||||
and for pull requests. A plain push to `dev` runs preflight, frontend (types,
|
|
||||||
units, build, bundle sync, no-new-any, no-new-private-writes), the narrow TS/Python geometry parity
|
|
||||||
guard when its inputs changed, backend, hacs and hassfest. Screenshot
|
|
||||||
freshness in `check-docs` is likewise a warning on a plain push and an error on
|
|
||||||
the candidate; `publish-prerelease.yml` and `release.yml` refuse a candidate
|
|
||||||
without the `Release:` trailer, so a green Validate without the heavy jobs can
|
|
||||||
never pass for a release.
|
|
||||||
|
|
||||||
During the implementation cycle the fast gates always run. Since 2026-08-14 the
|
|
||||||
owner's machine also carries Playwright with Chromium (Windows) and a full WSL
|
|
||||||
environment, which changes one thing (#151): **before moving an issue to
|
|
||||||
`S7-code-review`, run the smokes named in its AC locally** — `node
|
|
||||||
demo/smoke_<name>.mjs`. A red smoke that reaches the review costs a cycle; run
|
|
||||||
locally it costs a minute. Precedent: on #89 a fixture error lived through a
|
|
||||||
whole review round that a local run would have caught immediately.
|
|
||||||
|
|
||||||
**A smoke enters through the public surface (#629)**: contract `data-hp` hooks,
|
|
||||||
HA/fixture events and the harness facade `window.__hpTest`
|
|
||||||
(`docs/TESTING.md`); private card fields are read-only in assertions, and a new
|
|
||||||
write needs `// private-ok: <reason>` or the `no-new-private-writes` gate fails.
|
|
||||||
|
|
||||||
**One handoff, one push (#510).** Run `node scripts/process-gate.mjs --issues`
|
|
||||||
locally with `gh` available before pushing (without `gh` the hook cannot check the
|
|
||||||
issue status and stays silent). After `S7-code-review` do not push to the branch
|
|
||||||
until the verdict or the return arrives: a push on top of a running review cancels
|
|
||||||
it (10–20 runner minutes) and, after the material is fixed, also the merge (#312).
|
|
||||||
Set `S7` once per round, not after every CI fix: the pipeline now runs Validate
|
|
||||||
with the diff mutants on the material itself and returns a red one to `S6` without
|
|
||||||
spending a review cycle; since #636 it does not sleep while Validate runs — the
|
|
||||||
prepare stage leaves a sealed `review-pending` marker and exits, and the
|
|
||||||
completion of Validate (`process-resume.yml`, `workflow_run`) re-applies `S7`
|
|
||||||
so a fresh run finds the finished dispatch; `process-reconcile.yml` is the
|
|
||||||
fallback for a lost event. A second `S7` from the pipeline itself is therefore
|
|
||||||
normal and is not a new round. Mutants by diff run only where they are explicitly
|
|
||||||
requested — the review candidate, the merge candidate (both dispatch Validate
|
|
||||||
with `mutants=true`) and PRs (#510, #601). Ordinary pushes, the beta candidate
|
|
||||||
(`Release:` trailer) and `full=true` do not request them: a routine push costs
|
|
||||||
~3 minutes (08–09.09 mutants cost 48 of 56 Validate job-hours and were mostly
|
|
||||||
cancelled by the next push), and by the beta every issue has already been
|
|
||||||
mutated twice — on review and on the rebased merge candidate; the night runs
|
|
||||||
the full registry (`mutation-gate.yml`), not a diff subset.
|
|
||||||
|
|
||||||
**Workflows run from the default branch are thin callers (#623).** For
|
|
||||||
`issues`, `schedule` and `workflow_run` GitHub executes the file from `main`.
|
|
||||||
The six such files (`process.yml`, `process-resume.yml`,
|
|
||||||
`process-reconcile.yml`, `mutation-gate.yml`, `nightly.yml`,
|
|
||||||
`process-metrics.yml`) carry only triggers, run-name, permissions and
|
|
||||||
concurrency and call their body `_<name>.yml` at `@dev` with
|
|
||||||
`secrets: inherit`. A pipeline change is one commit to `dev` — edit the
|
|
||||||
`_<name>.yml` body; no mirror into `main`, no merge-back before promotion.
|
|
||||||
Only a change of triggers, dispatch inputs or the permission ceiling touches the
|
|
||||||
thin file, and then it is mirrored into `main` (preflight `workflow_sync`
|
|
||||||
compares exactly these six; `PROCESS.md` §10.4).
|
|
||||||
|
|
||||||
The full smoke set, `golden` and `performance_smoke` still belong to the
|
|
||||||
pre-beta run — which is then mandatory and complete. WSL runs of the full HA
|
|
||||||
harness (`~/houseplan-card`, venv) are advisory; **the canon does not move**:
|
|
||||||
the beta gate is CI at the exact SHA.
|
|
||||||
|
|
||||||
**Verifying and capturing are different things (#455).** `golden:verify` is
|
|
||||||
advisory and legal anywhere, Windows included: it reports differences and
|
|
||||||
accepts nothing. **Capturing** frames is refused outside Linux
|
|
||||||
before the browser even starts — `golden:capture` through
|
|
||||||
`demo/golden/policy.mjs`, documentation screenshots through
|
|
||||||
`npm run docs:capture` (use that script, not a bare `node
|
|
||||||
demo/docs/capture.mjs`: the gate sits one step earlier because editing the
|
|
||||||
capture script invalidates the committed screenshot index). The refusal prints
|
|
||||||
the WSL command. The reason is not policy but physics: Windows
|
|
||||||
rasterizes text through DirectWrite with different subpixel and DPI behaviour,
|
|
||||||
so no frame ever matches an accepted baseline byte for byte, no environment
|
|
||||||
witness can exist, and acceptance would refuse anyway (#401 accepts any
|
|
||||||
environment that proves itself with byte-identical undeclared frames — Linux is
|
|
||||||
simply the only one we have). The deliberate override is
|
|
||||||
`HP_ALLOW_FOREIGN_CAPTURE="reason"`; the reason travels into the output and the
|
|
||||||
manifest. Baselines are still accepted only via
|
|
||||||
`npm run golden:accept -- --reviewed` on a complete artefact. A local Linux
|
|
||||||
capture additionally requires the self-hashed WSL passport produced by
|
|
||||||
`npm run golden:wsl:capture`; plain `golden:capture` remains diagnostic. The
|
|
||||||
passport proves a clean published branch SHA, WSL/ext4, pinned toolchain,
|
|
||||||
current fingerprint, complete matrix, PNG hashes and witness floor. The
|
|
||||||
accepted index records that local provenance separately from a GitHub run,
|
|
||||||
and a final full GitHub Validate on the accepted exact SHA remains mandatory.
|
|
||||||
The one local
|
|
||||||
shortcut is `npm run docs:accept -- --identical` (#512): it re-captures on this
|
|
||||||
machine, compares decoded pixels with the committed frames and, only when every
|
|
||||||
frame is identical, refreshes the manifest fingerprints — frames that differ go
|
|
||||||
through the artefact as before. The displayed card version reaches the DOM
|
|
||||||
through `displayVersion()` (`src/card-version.ts`); the global
|
|
||||||
`__HP_VERSION_OVERRIDE__` behind it is for harnesses only and the product never
|
|
||||||
sets it.
|
|
||||||
|
|
||||||
**Backend.** A full Home Assistant harness cannot run on native Windows at all:
|
|
||||||
Home Assistant imports the Unix-only `fcntl` module. Its canon is Linux CI or WSL.
|
|
||||||
Locally only the pure subset runs; `python -m pytest tests_backend/ -q` without
|
|
||||||
Home Assistant **does not collect** `test_ha_*.py` at all (`conftest.py`
|
|
||||||
`collect_ignore_glob` when `homeassistant` is not importable — not a skip) and, since
|
|
||||||
#630, prints `HA harness NOT collected: N files (M tests)` with the canon to run
|
|
||||||
them. A green result there proves nothing about the harness. Say so in the report
|
|
||||||
instead of claiming the backend was verified. Cloud agents have the harness
|
|
||||||
at `.venv-backend/bin/python`.
|
|
||||||
|
|
||||||
**Running the app / smoke suite**: build a fresh bundle and copy it into the demo
|
|
||||||
assets first, then run `node demo/smoke_*.mjs`. No real Home Assistant server is
|
|
||||||
required: `demo/srv/demo.html` stubs `hass`, registries and `callService`.
|
|
||||||
|
|
||||||
**Golden images**: `npm run golden:capture` and `npm run golden:verify` refuse a
|
|
||||||
stale demo bundle. Build and copy first, then review `artifacts/golden/actual/` and
|
|
||||||
`diff/`. Update baselines only with `npm run golden:accept -- --reviewed`, using a
|
|
||||||
complete Linux CI artifact or the attested artefact made in the WSL/ext4 clone by
|
|
||||||
`npm run golden:wsl:capture`; never accept a partial scenario or images merely to
|
|
||||||
make CI green. The local route saves the first expected-red CI round trip only:
|
|
||||||
the accepted commit still needs a full GitHub Validate on its exact SHA. See
|
|
||||||
`demo/golden/README.md`.
|
|
||||||
|
|
||||||
**Freshness contract**: the embedded fingerprint covers `src/` plus Rollup,
|
|
||||||
TypeScript and package-lock build inputs. Every browser check must verify it
|
|
||||||
before trusting a result — benchmarks, golden runs and documentation captures
|
|
||||||
call `assertFreshDemoBundle` themselves, and smokes get it from `launch()` in
|
|
||||||
`demo/serve.mjs` (#236). A missing or mismatched fingerprint is a hard failure,
|
|
||||||
not a warning; `HP_ALLOW_STALE_BUNDLE=1` skips the check for debugging and says
|
|
||||||
so out loud. A smoke against a stale bundle does not fail cleanly: part of its
|
|
||||||
assertions go red and part stay green, which reads as a logic defect.
|
|
||||||
|
|
||||||
**CI is pinned to an exact SHA.** The release gate accepts only a `completed
|
|
||||||
success` run for the candidate's SHA, not "the last green one"; a new push cancels
|
|
||||||
an unfinished Validate for the same branch. Gate jobs, matching the actual
|
|
||||||
`validate.yml` (#191): `docs`, `provenance`, `process-gate`, `hacs`, `hassfest`,
|
|
||||||
`frontend`, `smoke`, `golden`, `performance_smoke`, `geometry_parity`, `backend`.
|
|
||||||
The `changes` job
|
|
||||||
is a service path-filter, not a gate. `docs` is a real blocker: it checks the
|
|
||||||
screenshots `sourceFingerprint` against current `src/**`, which is exactly what
|
|
||||||
went red after the #113 merge. A stable release additionally waits for Full
|
|
||||||
Performance and for a green E2E run on a real Home Assistant (`houseplan-e2e`,
|
|
||||||
dispatched on the candidate SHA by `release.yml`, #514/#540); betas and the
|
|
||||||
development cycle never run E2E. Stable installable assets (`houseplan.zip`,
|
|
||||||
`houseplan-card.js` and their `SHA256SUMS`) reach the public stable release only
|
|
||||||
from `release.yml` after those gates; a release published by hand is turned back
|
|
||||||
into a draft first (#540). The prerelease publisher additionally ships a
|
|
||||||
candidate-bound `RELEASE-MEMBERSHIP.json` covered by the same passport (#547). Before
|
|
||||||
every stable release `release.yml` also queues an independent review of the
|
|
||||||
whole beta line (`release-review.yml`, `PROCESS.md` §11.5, #638): no specs, no
|
|
||||||
review rounds, output `docs/reviews/RELEASE-REVIEW-vX.Y.Z.md`; it runs in
|
|
||||||
parallel and never blocks the release.
|
|
||||||
|
|
||||||
**"Verified" without a named command and its result is not evidence.**
|
|
||||||
|
|
||||||
## Environments
|
## Environments
|
||||||
|
|
||||||
**Local Windows checkout** is the day-to-day environment: repository-pinned Node
|
The owner's Windows machine: `.\scripts\windows-toolchain.ps1 setup|check` owns
|
||||||
and Python as in CI (`npm run toolchain:check` compares the machine with the pins
|
the pinned Node and Python; WSL works from an ext4 clone with
|
||||||
CI actually uses — `.nvmrc` and `.python-version` are derived from the same
|
`bash scripts/wsl-setup.sh --verify` (`docs/DEVELOPMENT.md` › Local Windows
|
||||||
sources, #496),
|
workstation). `npm run toolchain:check` compares any machine with the pins CI
|
||||||
`gh` authenticated. On the owner's machine do not trust the ambient PATH:
|
uses. The full Home Assistant harness cannot run on native Windows; without an
|
||||||
`.\scripts\windows-toolchain.ps1 setup|check` owns a verified portable Node and
|
importable `homeassistant` pytest does not collect `test_ha_*.py` at all, so a
|
||||||
dedicated `.venv-ci`, and its `npm`/`node`/`python`/`playwright` actions are the
|
green pure run proves nothing about the harness (`docs/TESTING.md`). Cloud
|
||||||
explicit pinned entrypoints (#557). It changes no persistent PATH and never
|
agents have the harness at `.venv-backend/bin/python`.
|
||||||
deletes a mismatched venv. In WSL, work from an ext4 clone and use
|
|
||||||
`bash scripts/wsl-setup.sh --verify` for the real HA subset plus one Linux visual
|
|
||||||
capture; exact-SHA Linux CI remains authoritative. `.venv-backend` does **not**
|
|
||||||
exist there — it is provisioned only by cloud agent startup scripts, which also
|
|
||||||
run `npm ci` and install Playwright Chromium.
|
|
||||||
|
|
||||||
Known environment-sensitive smoke: `demo/smoke_opening_measure.mjs` fails two
|
|
||||||
sub-checks (`place_dialog_x_magnetised`, `place_committed_x_center`) under the pinned
|
|
||||||
Chromium — a `1e-6`-tolerance magnet-snap on the opening-*placement* path. It
|
|
||||||
reproduces against the pristine committed bundle, so treat it as
|
|
||||||
pre-existing/pixel-precision, not a regression you introduced.
|
|
||||||
|
|
||||||
## Alpha experiments
|
|
||||||
|
|
||||||
`src/labs.ts` is the single registry and resolver for hidden presentation
|
|
||||||
experiments. One browser-local switch controls the complete set known to the
|
|
||||||
installed build: `hp_alpha=1` in query or the shared hash grammar enables and
|
|
||||||
persists it, while `hp_alpha=0` disables and persists it. Do not add per-feature
|
|
||||||
URL/storage keys or a YAML/config switch. The legacy `hp-labs` grammar and
|
|
||||||
`houseplan_card_labs_v1` storage are ignored and never migrated.
|
|
||||||
|
|
||||||
A new registry entry needs a unique lowercase id, issue, summary and
|
|
||||||
unit/browser coverage; it does not get `since` or `expires`. Invalid or duplicate
|
|
||||||
entries fail closed. Alpha capabilities may alter presentation only and must not
|
|
||||||
gate data, migrations, stores, HA actions or network calls. Current renderer
|
|
||||||
details are in `docs/ISOMETRIC.md`.
|
|
||||||
|
|
||||||
Demo harness render quirk: the fake `hass` in `demo.html` is set once, so opening the
|
|
||||||
page directly in a browser renders the floor plan but **device icons only appear
|
|
||||||
after a re-render** (an F5 refresh, or nudging `card.hass = {...card.hass}`). The
|
|
||||||
smoke launcher `demo/serve.mjs` already does this nudge; a plain browser session does
|
|
||||||
not. This is a harness limitation, not a card bug.
|
|
||||||
|
|
||||||
## Promotion rule
|
|
||||||
|
|
||||||
Every new feature or material behaviour change must be published as a beta/RC
|
|
||||||
before it can enter a stable release, even when its local audit is clean. The
|
|
||||||
stable release commit is promotion-only: version fields, generated bundle
|
|
||||||
snapshots and changelog/release metadata. Do not add feature source code in
|
|
||||||
that commit. An explicit owner-requested emergency hotfix is the only exception
|
|
||||||
and must be called out in the release handoff.
|
|
||||||
|
|
||||||
A `Release vX.Y.Z-beta.N candidate` commit is **not** promotion-only: it carries
|
|
||||||
the work itself and follows the ordinary rules, trailers included.
|
|
||||||
|
|
||||||
Issues are closed in a batch when a beta ships, not when implementation ends: that
|
|
||||||
way a bug found in the beta returns to the same task, and the beta announcement can
|
|
||||||
list what went in. The batch is immutable candidate membership proven by Git
|
|
||||||
trailers, never the mutable S8 queue at close time; authorship does not change
|
|
||||||
membership. Status labels are stripped as the issues close, and retries resume the
|
|
||||||
same manifest without duplicating the release comment (#547).
|
|
||||||
|
|||||||
+12
-17
@@ -67,8 +67,9 @@ artifact>`; when a change cannot move a pixel, `npm run docs:accept --
|
|||||||
--identical` re-captures locally, compares decoded pixels and refreshes only the
|
--identical` re-captures locally, compares decoded pixels and refreshes only the
|
||||||
source fingerprint. Scenario version, source fingerprint and every image hash
|
source fingerprint. Scenario version, source fingerprint and every image hash
|
||||||
are recorded in the [screenshot index](docs/images/screenshots.json), and
|
are recorded in the [screenshot index](docs/images/screenshots.json), and
|
||||||
`node scripts/check-docs.mjs` reports a stale fingerprint before a beta
|
`node scripts/check-docs.mjs` reports a stale fingerprint: a warning on an
|
||||||
candidate. The full rule is in `PROCESS.md` (documentation screenshots).
|
ordinary push, an error on a beta candidate (a commit with a `Release:`
|
||||||
|
trailer). The full rule is in `PROCESS.md` §8.
|
||||||
|
|
||||||
## Where to ask
|
## Where to ask
|
||||||
|
|
||||||
@@ -100,16 +101,13 @@ npm install # also installs .githooks through the prepare script
|
|||||||
|
|
||||||
### Why `--filter=blob:none` (#345)
|
### Why `--filter=blob:none` (#345)
|
||||||
|
|
||||||
A full clone is **215 MB of `.git`**; a blobless one is **26 MB** — measured, not
|
A blobless clone keeps every commit and tag, so ranges, `merge-base` and
|
||||||
estimated. Both carry all 1611 commits and all 182 tags, so ranges, `merge-base`
|
`git diff` across history work exactly as in a full clone; only historical *file
|
||||||
and `git diff` across history work identically; `git diff origin/dev~3..origin/dev`
|
contents* are fetched on demand. Most of the pack is exactly such content that
|
||||||
in a blobless clone takes about a second and grows `.git` by one megabyte.
|
almost nobody reads again — documentation screenshots re-captured with the UI and
|
||||||
|
the committed bundle rewritten by every release candidate — so a blobless clone
|
||||||
The difference is that historical *file contents* are fetched only if something
|
is several times smaller. To see the numbers for your own clone, compare
|
||||||
actually asks for them. That matters here because 32% of the pack is documentation
|
`git count-objects -vH` in a blobless and in a full one.
|
||||||
screenshots — ten PNGs re-captured 196 times — and another sizeable share is the
|
|
||||||
committed bundle, one 1.16 MB file per product change. Almost nobody ever reads an
|
|
||||||
old revision of either.
|
|
||||||
|
|
||||||
Drop the flag if you work offline with history, or need `git log -p` over the whole
|
Drop the flag if you work offline with history, or need `git log -p` over the whole
|
||||||
tree repeatedly. Do **not** replace it with `--depth=1`: a shallow clone is about
|
tree repeatedly. Do **not** replace it with `--depth=1`: a shallow clone is about
|
||||||
@@ -142,8 +140,5 @@ Linux CI or WSL (`bash scripts/wsl-setup.sh --verify`). Without an importable
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
Start with `docs/ARCHITECTURE.md` (data model, WS API, coordinate system) and
|
Start with `docs/ARCHITECTURE.md` (data model, WS API, coordinate system) and
|
||||||
`docs/STATUS.md` (current state). Release mechanics live in `docs/STATUS.md` › Workflow: the version sources are
|
`docs/STATUS.md` (current state). Release mechanics live in one place:
|
||||||
the ones checked by `scripts/release-contract.mjs`, prereleases go through
|
`docs/DEVELOPMENT.md` › Release.
|
||||||
`npm run release:prerelease -- <tag> --issues=… --yes` (or the manual
|
|
||||||
`Publish prerelease` workflow), and stable installable assets are published only
|
|
||||||
by `release.yml` (#540).
|
|
||||||
|
|||||||
+24
-98
@@ -215,9 +215,9 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
|||||||
- **Скоуп не расширяется.** Найденное по пути становится новым issue в «Новое».
|
- **Скоуп не расширяется.** Найденное по пути становится новым issue в «Новое».
|
||||||
Если находка блокирует — текущий issue уходит в «Заблокировано» со ссылкой.
|
Если находка блокирует — текущий issue уходит в «Заблокировано» со ссылкой.
|
||||||
Попутных правок «раз уж я здесь» не бывает.
|
Попутных правок «раз уж я здесь» не бывает.
|
||||||
- **Документация — в том же коммите,** что и поведение (действующая политика
|
- **Документация — в том же коммите,** что и поведение: changelog RU+EN для
|
||||||
`docs/STATUS.md`): changelog RU+EN для пользовательского, `STATUS.md` для
|
пользовательского, `STATUS.md` для состояния, `DEVELOPMENT.md` для новых
|
||||||
состояния, `DEVELOPMENT.md` для новых грабель, `ARCHITECTURE.md` для дизайна.
|
грабель, `ARCHITECTURE.md` для дизайна.
|
||||||
- **Выход:** локальный гейт зелёный (§8), хендофф-комментарий (§7.2).
|
- **Выход:** локальный гейт зелёный (§8), хендофф-комментарий (§7.2).
|
||||||
|
|
||||||
### 2.7 Код-ревью
|
### 2.7 Код-ревью
|
||||||
@@ -273,7 +273,9 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
|||||||
видит разницу.
|
видит разницу.
|
||||||
- **High блокируют.** Medium **в скоупе задачи** чинится в текущем issue:
|
- **High блокируют.** Medium **в скоупе задачи** чинится в текущем issue:
|
||||||
без High это жёлтый вердикт и возврат автору, фикс проходит повторный цикл.
|
без High это жёлтый вердикт и возврат автору, фикс проходит повторный цикл.
|
||||||
Medium **вне скоупа** — отдельный issue (#202).
|
Medium **вне скоупа** — отдельный issue (#202). Жёлтый вердикт законен и
|
||||||
|
тогда, когда все AC выполнены, если изменение не решает заявленный сценарий
|
||||||
|
или ухудшает соседний.
|
||||||
- **Вердикт привязан к SHA (#312).** Все числа и факты отчёта сверяются с
|
- **Вердикт привязан к SHA (#312).** Все числа и факты отчёта сверяются с
|
||||||
`git rev-parse HEAD` непосредственно перед подведением итогов, а не с SHA,
|
`git rev-parse HEAD` непосредственно перед подведением итогов, а не с SHA,
|
||||||
зафиксированным в начале разбора: во время ревью в ветку может прилететь
|
зафиксированным в начале разбора: во время ревью в ветку может прилететь
|
||||||
@@ -286,8 +288,8 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
|||||||
вызовом в `test-build`, а не поиском строки в исходнике: текстовый якорь
|
вызовом в `test-build`, а не поиском строки в исходнике: текстовый якорь
|
||||||
краснеет на переносе метода без единой регрессии, и это делает вынос дороже,
|
краснеет на переносе метода без единой регрессии, и это делает вынос дороже,
|
||||||
чем оставить монолит как есть. Список тестов, читающих монолит как текст,
|
чем оставить монолит как есть. Список тестов, читающих монолит как текст,
|
||||||
заморожен (`test/monolith-text-anchors.test.mjs`, 54 файла на 23.09.2026) и
|
заморожен (`FROZEN_TEXT_ANCHOR_TESTS` в `test/monolith-text-anchors.test.mjs`)
|
||||||
может только уменьшаться; новое имя в нём — находка ревью, а не запись в
|
и может только уменьшаться; новое имя в нём — находка ревью, а не запись в
|
||||||
список. Связность монолита измеряется шестью числами
|
список. Связность монолита измеряется шестью числами
|
||||||
(`scripts/monolith-metrics.mjs`: делегаты, члены порта, `host.`, приватные
|
(`scripts/monolith-metrics.mjs`: делегаты, члены порта, `host.`, приватные
|
||||||
члены порта и харнесса, байты `dist/`), база — `scripts/monolith-baseline.json`;
|
члены порта и харнесса, байты `dist/`), база — `scripts/monolith-baseline.json`;
|
||||||
@@ -358,10 +360,10 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
|||||||
|
|
||||||
Находкой остаётся другое: **SHA, мёртвый уже в момент публикации отчёта** —
|
Находкой остаётся другое: **SHA, мёртвый уже в момент публикации отчёта** —
|
||||||
он означает, что значение сняли до `amend` или `rebase` и не сверили перед
|
он означает, что значение сняли до `amend` или `rebase` и не сверили перед
|
||||||
выводом, как требует §7.2. Это отличие не теоретическое: на #403 оба
|
выводом, как требует §2.7 («Вердикт привязан к SHA»). Это отличие не
|
||||||
источника, автор и ревьюер, независимо назвали один и тот же осиротевший
|
теоретическое: на #403 оба источника, автор и ревьюер, независимо назвали один
|
||||||
SHA, и следующий раунд восстанавливал коммит по содержимому диффа руками
|
и тот же осиротевший SHA, и следующий раунд восстанавливал коммит по
|
||||||
(issue #413). Конвейер теперь такую публикацию останавливает сам;
|
содержимому диффа руками (issue #413). Конвейер теперь такую публикацию останавливает сам;
|
||||||
3. по каждой находке предыдущего раунда показать, **чем именно она закрыта** —
|
3. по каждой находке предыдущего раунда показать, **чем именно она закрыта** —
|
||||||
строкой кода или текста, а не заявлением автора;
|
строкой кода или текста, а не заявлением автора;
|
||||||
4. заново проверять только те AC, чьё доказательство дельта задевает;
|
4. заново проверять только те AC, чьё доказательство дельта задевает;
|
||||||
@@ -404,7 +406,7 @@ dev, ни при публикации документа код-ревью: ин
|
|||||||
каждом раунде: код изменился, а стоят они минуты. Тяжёлые — по дельте (§10.2).
|
каждом раунде: код изменился, а стоят они минуты. Тяжёлые — по дельте (§10.2).
|
||||||
|
|
||||||
**Разбор остаётся полным**, если дельта не локальна: ребейз на ушедший вперёд
|
**Разбор остаётся полным**, если дельта не локальна: ребейз на ушедший вперёд
|
||||||
`dev` (после ребейза это другой код, §7.2), смена контракта поведения, задета
|
`dev` (после ребейза это другой код, §10.4), смена контракта поведения, задета
|
||||||
новая подсистема, либо объём дельты сопоставим с исходной задачей.
|
новая подсистема, либо объём дельты сопоставим с исходной задачей.
|
||||||
|
|
||||||
Сокращается объём **разбора, а не строгость**: правка по замечанию способна
|
Сокращается объём **разбора, а не строгость**: правка по замечанию способна
|
||||||
@@ -699,16 +701,6 @@ issue #NN
|
|||||||
прогон показал, почему это неверно: жёлтый там означал, что AC описывает неверное
|
прогон показал, почему это неверно: жёлтый там означал, что AC описывает неверное
|
||||||
изменение контракта — реализовать такое ТЗ значило бы сделать ошибку по инструкции.
|
изменение контракта — реализовать такое ТЗ значило бы сделать ошибку по инструкции.
|
||||||
|
|
||||||
### 7.3 Расхождения с текущим состоянием, которые надо закрыть
|
|
||||||
|
|
||||||
1. ✅ **Статус ТЗ дублировал статус issue.** Закрыто 2026-09-10 (#517): ТЗ живёт
|
|
||||||
в теле issue, `docs/specs/` — архив, индекс с колонкой «Статус ТЗ» удалён
|
|
||||||
вместе с самой таблицей. Статус задачи — только метка `S*`.
|
|
||||||
2. **Ревью до релиза 1.62 живут вне репозитория.** Документы `CODE-REVIEW-*.md` и
|
|
||||||
`SPEC-REVIEW-*.md` за прежний период лежат в папке владельца, и переносить их
|
|
||||||
задним числом смысла нет: они описывают код, которого уже нет. Новые документы
|
|
||||||
ревью кладёт в `docs/reviews/` сам конвейер, в ветку задачи.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Гейты
|
## 8. Гейты
|
||||||
@@ -734,10 +726,11 @@ npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
|
|||||||
# если менялось одно из зеркал junction limits
|
# если менялось одно из зеркал junction limits
|
||||||
```
|
```
|
||||||
|
|
||||||
**Новый код не добавляет `any`** (#342). В `src/**` уже 1034 вхождения явного
|
**Новый код не добавляет `any`** (#342). Явного `any` в `src/**` — сотни
|
||||||
`any` в 49 файлах; перетипизировать это одним заходом — месяц риска ради нуля
|
вхождений (`node scripts/no-new-any.mjs --total`); перетипизировать это одним
|
||||||
пользовательской ценности, поэтому долг снимается при плановом извлечении
|
заходом — месяц риска ради нуля пользовательской ценности, поэтому долг
|
||||||
подсистем (#425, прежний #34), а не разовой заменой. Гейт `scripts/no-new-any.mjs` судит
|
снимается при плановом извлечении подсистем (#425, прежний #34), а не разовой
|
||||||
|
заменой. Гейт `scripts/no-new-any.mjs` судит
|
||||||
**только добавленные строки**: существующий долг на нетронутой строке законен,
|
**только добавленные строки**: существующий долг на нетронутой строке законен,
|
||||||
правка строки со старым `any` — новая ответственность. Исключение объявляется на
|
правка строки со старым `any` — новая ответственность. Исключение объявляется на
|
||||||
той же строке, `// any-ok: <конкретная причина>`; голый маркер и причины вида
|
той же строке, `// any-ok: <конкретная причина>`; голый маркер и причины вида
|
||||||
@@ -776,8 +769,8 @@ npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
|
|||||||
фронтенда делает его устаревшим. Выборка «по diff и AC» здесь не работает — diff
|
фронтенда делает его устаревшим. Выборка «по diff и AC» здесь не работает — diff
|
||||||
всегда попадает, и решать нечего. Цена пропуска измерена: скриншоты не
|
всегда попадает, и решать нечего. Цена пропуска измерена: скриншоты не
|
||||||
пересняли в #230 и #234, и `dev` стоял с красным job `docs`, пока это не нашли
|
пересняли в #230 и #234, и `dev` стоял с красным job `docs`, пока это не нашли
|
||||||
при следующей задаче (#237). Пересъёмка — `npm run build && node
|
при следующей задаче (#237). Пересъёмка — по двум абзацам выше, коммит
|
||||||
demo/docs/capture.mjs`, коммит вместе с задачей.
|
вместе с задачей.
|
||||||
|
|
||||||
**Перф-смок в Validate зависит от диффа** (#473). Два glow-профиля
|
**Перф-смок в Validate зависит от диффа** (#473). Два glow-профиля
|
||||||
гоняются всегда; при правке `src/iso-*` добавляется `large-house-isometric-v1`,
|
гоняются всегда; при правке `src/iso-*` добавляется `large-house-isometric-v1`,
|
||||||
@@ -1143,7 +1136,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
|||||||
- ветка уже содержит весь `dev` — ничего;
|
- ветка уже содержит весь `dev` — ничего;
|
||||||
- отстала и ребейзится чисто — ребейз, `push --force-with-lease`, ревью по
|
- отстала и ребейзится чисто — ребейз, `push --force-with-lease`, ревью по
|
||||||
приведённому состоянию. Факт ребейза передаётся в промпт, чтобы сработало
|
приведённому состоянию. Факт ребейза передаётся в промпт, чтобы сработало
|
||||||
правило §7.2 о полном разборе вместо дельты;
|
правило §2.10 о полном разборе вместо дельты;
|
||||||
- конфликт — возврат в `S6-in-progress` **до** запуска ревью. Цикл при этом не
|
- конфликт — возврат в `S6-in-progress` **до** запуска ревью. Цикл при этом не
|
||||||
расходуется: код никто не читал, вердикта нет.
|
расходуется: код никто не читал, вердикта нет.
|
||||||
|
|
||||||
@@ -1165,7 +1158,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
|||||||
- если `dev` не двигался — push с `--force-with-lease` на текущую вершину;
|
- если `dev` не двигался — push с `--force-with-lease` на текущую вершину;
|
||||||
- если двигался — ребейз (конфликт — `S6-in-progress`, как раньше), сравнение
|
- если двигался — ребейз (конфликт — `S6-in-progress`, как раньше), сравнение
|
||||||
patch-id проверенного и получившегося диффа (различие — `S7-code-review`: вердикт
|
patch-id проверенного и получившегося диффа (различие — `S7-code-review`: вердикт
|
||||||
к другому диффу не применим, §7.2), публикация кандидата в ветку задачи, запуск
|
к другому диффу не применим, §2.10), публикация кандидата в ветку задачи, запуск
|
||||||
Validate с мутантами на ней (#510) и ожидание зелёного dispatch-прогона **на этом
|
Validate с мутантами на ней (#510) и ожидание зелёного dispatch-прогона **на этом
|
||||||
SHA** — push-прогон мутантов не несёт — и только затем push в `dev` с lease на ту
|
SHA** — push-прогон мутантов не несёт — и только затем push в `dev` с lease на ту
|
||||||
вершину, поверх которой кандидат собран. Отклонённый lease — `dev` двинулся снова
|
вершину, поверх которой кандидат собран. Отклонённый lease — `dev` двинулся снова
|
||||||
@@ -1215,7 +1208,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
|||||||
повторно **без вызова модели**, если последний документ этапа несёт записанный
|
повторно **без вызова модели**, если последний документ этапа несёт записанный
|
||||||
конвейером вердикт `green` с High 0 и дерево материала не изменилось ни в одном
|
конвейером вердикт `green` с High 0 и дерево материала не изменилось ни в одном
|
||||||
файле вне `docs/reviews/**` (сравнивает `git diff` по содержимому). Ребейз, правка
|
файле вне `docs/reviews/**` (сравнивает `git diff` по содержимому). Ребейз, правка
|
||||||
теста, фикстуры или ТЗ дают отличие дерева и полный разбор — правило §7.2 не
|
теста, фикстуры или ТЗ дают отличие дерева и полный разбор — правило §2.10 не
|
||||||
ослабляется, оно просто не касается дерева, которое уже читали.
|
ослабляется, оно просто не касается дерева, которое уже читали.
|
||||||
|
|
||||||
Цикл считается **по этапу**: вердикт по ТЗ не расходует бюджет код-ревью. Раньше
|
Цикл считается **по этапу**: вердикт по ТЗ не расходует бюджет код-ревью. Раньше
|
||||||
@@ -1236,7 +1229,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
|||||||
- issue создан в **той же сессии до коммита**, метка `hotfix`;
|
- issue создан в **той же сессии до коммита**, метка `hotfix`;
|
||||||
- ТЗ «как сделано» + раздел «почему нельзя было ждать»;
|
- ТЗ «как сделано» + раздел «почему нельзя было ждать»;
|
||||||
- в течение 24 часов задача ретроспективно проходит код-ревью;
|
- в течение 24 часов задача ретроспективно проходит код-ревью;
|
||||||
- аварийность названа явно в релизном хендоффе (действующее правило `AGENTS.md`).
|
- аварийность названа явно в релизном хендоффе.
|
||||||
|
|
||||||
### 11.3 Гигиена репозитория
|
### 11.3 Гигиена репозитория
|
||||||
|
|
||||||
@@ -1378,70 +1371,3 @@ Golden, браузерные смоки, performance и полный HA-харн
|
|||||||
|
|
||||||
**Нарушение процесса — тоже issue** (метка `process`): если правило удалось
|
**Нарушение процесса — тоже issue** (метка `process`): если правило удалось
|
||||||
нарушить незаметно, виновата проверка.
|
нарушить незаметно, виновата проверка.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 13. Внедрение
|
|
||||||
|
|
||||||
Состояние на 2026-08-13.
|
|
||||||
|
|
||||||
1. ✅ **Метки созданы, бэклог размечен.** У всех открытых issue владельца ровно
|
|
||||||
одна `S*`-метка, инварианты чистые.
|
|
||||||
2. ✅ **Колонка «Статус ТЗ» убрана** — вместе со всем индексом: `docs/specs/`
|
|
||||||
стал архивом, ТЗ переехало в тело issue (#517, 2026-09-10). Перенос старых
|
|
||||||
документов ревью в `docs/reviews/` отменён: они описывают код, которого уже нет.
|
|
||||||
3. ✅ **Гейт написан** — `scripts/process-gate.mjs` плюс job в `validate.yml`,
|
|
||||||
issue #105. Прошёл **вне** флоу как инфраструктурная задача (§1, issue #118), а
|
|
||||||
не через ТЗ и ревью, как предполагала прежняя редакция этого пункта.
|
|
||||||
4. ✅ **Долг ревью списан решением владельца.** Беты `beta.2`…`beta.10` сделаны по
|
|
||||||
прежнему процессу и не пересматриваются. Точка отсчёта — релиз 1.62.0; отсчёт
|
|
||||||
начинается с первой беты следующей линии.
|
|
||||||
5. ⏳ Завести issue на находку «смок `visual_continuity` не умеет падать» — это
|
|
||||||
ровно тот класс дефектов, который в процессе без ручного тестирования стоит
|
|
||||||
дороже всего.
|
|
||||||
6. ✅ `BACKLOG-2026-08-11.md` — разовый отчёт, решения живут в issue.
|
|
||||||
7. ✅ `AGENTS.md` переписан целиком, шире блока §14.
|
|
||||||
8. ✅ **Канон перенесён в репозиторий** (issue #112). До этого полный процесс жил
|
|
||||||
только в папке владельца, а в репозитории лежал файл на 51 строку про трейлеры
|
|
||||||
коммитов — из свежего клона канон не был виден вообще.
|
|
||||||
9. ✅ **`pre-push` написан** (§10.1, issue #121). Блокирующая проверка на клиенте
|
|
||||||
есть; обойти её можно только `--no-verify`, и тогда то же найдёт CI.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 14. Блок для AGENTS.md
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Процесс: код только через issue
|
|
||||||
|
|
||||||
Изменение продуктового кода без issue запрещено. Код меняется только из статуса
|
|
||||||
«Готово к разработке» или дальше. Полные правила, критерии статусов и гейты —
|
|
||||||
`docs/PROCESS.md`, читать до начала работы.
|
|
||||||
|
|
||||||
Жизненный цикл (статус = метка issue): `S1-new` → `S2-analysis` → `S3-spec` →
|
|
||||||
`S4-spec-review` → `S5-ready` → `S6-in-progress` → `S7-code-review` → `S8-merged`
|
|
||||||
→ закрытие пачкой при выпуске беты. Оба ревью возвращают на правки не более 4
|
|
||||||
циклов; пятый заход — разбор у владельца (разделить / отклонить / арбитраж).
|
|
||||||
|
|
||||||
Инфраструктурная задача (ни одного файла класса A) делается сразу любым агентом:
|
|
||||||
без `S*` → `S7-code-review` ↔ `S6-in-progress` → `S8-merged`. ТЗ и ревью ТЗ нет,
|
|
||||||
код-ревью обязательно.
|
|
||||||
|
|
||||||
Ревью запускается **само** от меток `S4-spec-review` и `S7-code-review`.
|
|
||||||
Deterministic prerequisites, модель и интеграция имеют отдельные пределы 55/45/55
|
|
||||||
минут; обычно стадии заканчиваются существенно раньше. Поставив такую метку, автор
|
|
||||||
не заканчивает работу, а ждёт смены метки опросом и продолжает по тому, чем она
|
|
||||||
стала.
|
|
||||||
|
|
||||||
- ветка `issue/<NN>-<slug>`, коммиты с трейлерами `Issue: #NN` и `User-Visible: yes|no`;
|
|
||||||
- работаем прямыми коммитами в `dev`, без PR: блокирующий гейт — локальный
|
|
||||||
`pre-push` (ставится автоматически через `npm ci`), CI — страховка. Force-push
|
|
||||||
в `dev` запрещён;
|
|
||||||
- автор ≠ ревьюер, ни для ТЗ, ни для кода;
|
|
||||||
- фазы ручного тестирования нет: автотесты пишутся в реализации, AC проверяет
|
|
||||||
код-ревью, найденные позже дефекты — новые issue типа «баг»;
|
|
||||||
- мелкие задачи (метка `small`, сложность ≤3): ТЗ в теле issue, ревью ТЗ
|
|
||||||
комментарием, код-ревью — как обычно;
|
|
||||||
- найденное вне скоупа — новый issue, а не попутная правка;
|
|
||||||
- issue закрывает релиз-менеджер после выпуска беты, не исполнитель.
|
|
||||||
```
|
|
||||||
|
|||||||
+60
-27
@@ -146,15 +146,6 @@ git config core.fsmonitor true
|
|||||||
git config core.untrackedCache true
|
git config core.untrackedCache true
|
||||||
```
|
```
|
||||||
|
|
||||||
### ⚠️ File-sync pitfalls (critical)
|
|
||||||
1. The network mount sometimes serves files **truncated/scrambled** — edits via the Edit tool
|
|
||||||
from the Windows side are unreliable. Rule: **apply python patches against a clean copy in /tmp,
|
|
||||||
write via bash**, with an assert that count(old)==1.
|
|
||||||
2. **Run the rollup build ONLY in /tmp/hpc** (`npm ci` is already done). A build on the mount once
|
|
||||||
produced a syntactically valid but broken bundle ("wi is not defined") that crashed the rendering
|
|
||||||
of ALL HA dashboards (the card is loaded as an extra_module on every page!).
|
|
||||||
3. `.git` cannot be created on the mount ("Operation not permitted" on dot-directories) — hence the bundle.
|
|
||||||
|
|
||||||
## Local repository maintenance (#628)
|
## Local repository maintenance (#628)
|
||||||
|
|
||||||
Owner decision on 2026-09-25: use only a one-time local garbage collection for
|
Owner decision on 2026-09-25: use only a one-time local garbage collection for
|
||||||
@@ -238,7 +229,7 @@ on with `window.__hpTest.setVolumetricView(true)`.
|
|||||||
|
|
||||||
To add a capability, add one unique lowercase id plus issue and a non-empty
|
To add a capability, add one unique lowercase id plus issue and a non-empty
|
||||||
summary to `LABS_FLAGS`, then cover registry validation and the alpha-on active
|
summary to `LABS_FLAGS`, then cover registry validation and the alpha-on active
|
||||||
set. Capabilities have no individual public key or version lifetime: the one
|
set; invalid or duplicate entries fail closed. Capabilities have no individual public key or version lifetime: the one
|
||||||
persisted alpha switch is deliberately indefinite until the owner changes the
|
persisted alpha switch is deliberately indefinite until the owner changes the
|
||||||
contract. See `docs/ISOMETRIC.md` for the current use.
|
contract. See `docs/ISOMETRIC.md` for the current use.
|
||||||
|
|
||||||
@@ -313,14 +304,15 @@ smoke launcher continues to default to the current repository root.
|
|||||||
Both browser diagnostics require a freshly built/copied demo bundle. Rollup
|
Both browser diagnostics require a freshly built/copied demo bundle. Rollup
|
||||||
embeds a SHA-256 fingerprint of `src/` plus the locked package and
|
embeds a SHA-256 fingerprint of `src/` plus the locked package and
|
||||||
Rollup/TypeScript build inputs; benchmark/golden runners fail before
|
Rollup/TypeScript build inputs; benchmark/golden runners fail before
|
||||||
capturing anything when `demo/srv/assets/houseplan-card.js` is stale. Golden
|
capturing anything when `demo/srv/assets/houseplan-card.js` is stale;
|
||||||
commands and the explicit review workflow are documented in
|
`demo/bundle-freshness.mjs` also verifies every manifest-listed asset hash, so a
|
||||||
`demo/golden/README.md`.
|
partially copied tree fails too. Golden commands and the explicit review
|
||||||
|
workflow are documented in `demo/golden/README.md`.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /tmp/hpc && npm ci # once
|
npm ci # once
|
||||||
npm run bundle:sync # build + entry/manifest/chunks → demo
|
npm run bundle:sync # build + entry/manifest/chunks → demo
|
||||||
npm run bundle:budget # initial View graph within INITIAL_VIEW_GZIP_BUDGET (scripts/bundle-budget.mjs)
|
npm run bundle:budget # initial View graph within INITIAL_VIEW_GZIP_BUDGET (scripts/bundle-budget.mjs)
|
||||||
npm run bundle:clean # before an ordinary commit (#657)
|
npm run bundle:clean # before an ordinary commit (#657)
|
||||||
@@ -516,6 +508,18 @@ edits — not a commit, not a merge (that is decided in `integrate` from the sea
|
|||||||
|
|
||||||
## Release
|
## Release
|
||||||
|
|
||||||
|
This section is the only home of the release mechanics; `docs/STATUS.md`,
|
||||||
|
`docs/ARCHITECTURE.md`, `AGENTS.md` and `CONTRIBUTING.md` link here instead of
|
||||||
|
retelling it. The process side — when an issue closes, what a stable commit may
|
||||||
|
contain, the independent line review — is `PROCESS.md` §2.8, §3 and §11.5.
|
||||||
|
|
||||||
|
The shape in one paragraph: a pre-release is one tested `dev` commit and tag
|
||||||
|
with `prerelease=true`, published from `dev`; `main` stays untouched. A stable
|
||||||
|
release fast-forwards `main` to the exact tested `dev` SHA and is produced only
|
||||||
|
by `release.yml`. Installations then update through HACS by tag («Deployment»
|
||||||
|
above); the dev stand takes the head of `dev` from the `dev-build` branch
|
||||||
|
(`demo/stand/README.md`).
|
||||||
|
|
||||||
### Primary prerelease path
|
### Primary prerelease path
|
||||||
|
|
||||||
Prepare the candidate as usual: synchronize every version field, add dated RU
|
Prepare the candidate as usual: synchronize every version field, add dated RU
|
||||||
@@ -541,7 +545,9 @@ npm run release:check -- v1.61.0-beta.4 --issues=63,64
|
|||||||
```
|
```
|
||||||
|
|
||||||
The orchestrator requires a clean, synchronized `dev`, byte-identical bundle
|
The orchestrator requires a clean, synchronized `dev`, byte-identical bundle
|
||||||
snapshots and a completed green Validate for `HEAD`. Snapshot hashes and the
|
snapshots and a completed green Validate for `HEAD`; like `release.yml`, it
|
||||||
|
refuses a candidate without the `Release:` trailer and a committed bundle whose
|
||||||
|
embedded source fingerprint differs from the tree. Snapshot hashes and the
|
||||||
uploaded standalone JS are read from the exact Git blobs rather than checkout
|
uploaded standalone JS are read from the exact Git blobs rather than checkout
|
||||||
bytes, so Windows CRLF conversion cannot disagree with the LF-tagged archive.
|
bytes, so Windows CRLF conversion cannot disagree with the LF-tagged archive.
|
||||||
The archive command additionally forces `core.autocrlf=false` for that one
|
The archive command additionally forces `core.autocrlf=false` for that one
|
||||||
@@ -631,7 +637,10 @@ real Home Assistant — `e2e-gate.mjs --ref=<sha>` dispatches `e2e.yml` in
|
|||||||
(#514, #540) — then builds once, archives `houseplan.zip` from that same tree
|
(#514, #540) — then builds once, archives `houseplan.zip` from that same tree
|
||||||
(`git archive <sha>:custom_components/houseplan`, deterministic), writes
|
(`git archive <sha>:custom_components/houseplan`, deterministic), writes
|
||||||
`SHA256SUMS`, uploads everything into a draft, publishes, downloads the public
|
`SHA256SUMS`, uploads everything into a draft, publishes, downloads the public
|
||||||
assets back and checks them against the passport, and only then announces. The
|
assets back and checks them against the passport, and only then announces.
|
||||||
|
Before staging, a stable release also runs `npm run continuity:screencast`: the
|
||||||
|
CDP compositor screencast fails the run on an empty or black presented frame and
|
||||||
|
uploads the failed frames as `continuity-screencast`. The
|
||||||
tree hash printed in the run summary is the identity between what E2E installed
|
tree hash printed in the run summary is the identity between what E2E installed
|
||||||
and what HACS downloads.
|
and what HACS downloads.
|
||||||
|
|
||||||
@@ -644,12 +653,16 @@ then cut a new tag. Re-dispatching the workflow on an already public tag is a
|
|||||||
**repair**: the gates run again on the SHA, missing assets are added, and an
|
**repair**: the gates run again on the SHA, missing assets are added, and an
|
||||||
existing asset whose hash differs from the rebuilt one fails the run instead of
|
existing asset whose hash differs from the rebuilt one fails the run instead of
|
||||||
being replaced. Hand-published betas are ignored by this workflow — prereleases
|
being replaced. Hand-published betas are ignored by this workflow — prereleases
|
||||||
have their own staged path above. Bump the version
|
have their own staged path above. Bump the version in every source
|
||||||
everywhere in sync: `src/houseplan-card.ts` (CARD_VERSION), `package.json`,
|
`scripts/release-contract.mjs` reads (`parseVersionSources`: `package.json`,
|
||||||
`custom_components/houseplan/manifest.json`, `custom_components/houseplan/const.py`.
|
`package-lock.json`, the integration `manifest.json` and `const.py`,
|
||||||
|
`CARD_VERSION` in the card and in the editor runtime); the snapshot table in
|
||||||
|
`docs/STATUS.md` reports whether they agree.
|
||||||
|
|
||||||
Validate intentionally runs on branch pushes, not tag pushes, so an annotated
|
Validate intentionally runs on branch pushes, not tag pushes, so an annotated
|
||||||
release tag does not duplicate the expensive browser/performance matrix. Every
|
release tag does not duplicate the expensive browser/performance matrix; a new
|
||||||
|
push cancels an unfinished Validate for the same branch, and the gates accept
|
||||||
|
only a completed run for the exact SHA, never "the last green one". Every
|
||||||
tagged SHA must therefore already be pushed to a branch and have a completed
|
tagged SHA must therefore already be pushed to a branch and have a completed
|
||||||
green full exact-SHA Validate proof. For an owner-approved emergency hotfix, push a
|
green full exact-SHA Validate proof. For an owner-approved emergency hotfix, push a
|
||||||
temporary `hotfix/*` branch and wait for Validate before creating the tag;
|
temporary `hotfix/*` branch and wait for Validate before creating the tag;
|
||||||
@@ -667,6 +680,18 @@ bodies of skipped releases. The full detail remains in both
|
|||||||
`docs/CHANGELOG.ru.md` and `docs/CHANGELOG.md`; finish every release body with
|
`docs/CHANGELOG.ru.md` and `docs/CHANGELOG.md`; finish every release body with
|
||||||
two explicit links, one to each language version of the changelog.
|
two explicit links, one to each language version of the changelog.
|
||||||
|
|
||||||
|
A **stable** body aggregates the changelog since the previous **stable**
|
||||||
|
release, never since the last beta (#328, owner rules 2026-08-27): everything
|
||||||
|
the line's beta changelogs describe reaches it, while a bug introduced and fixed
|
||||||
|
strictly inside the beta line — never shipped in any stable — stays out. Every
|
||||||
|
bullet links its GitHub issue so the rules stay machine-checkable. Draft with
|
||||||
|
`npm run release:notes -- <tag>` (it lists each candidate item with its source
|
||||||
|
section so the curator can strike in-line-only fixes), curate by hand, then
|
||||||
|
`npm run release:notes -- <tag> --verify` must pass. The small-fixes bullet is
|
||||||
|
allowed only when the range really contains user-visible work the body does not
|
||||||
|
itemise; a single-issue hotfix ships without it (the verifier enforces this).
|
||||||
|
Open or partially delivered issues are never presented as shipped.
|
||||||
|
|
||||||
Changelog entries may link directly to a **closed** GitHub Issue when that
|
Changelog entries may link directly to a **closed** GitHub Issue when that
|
||||||
issue is the canonical task for the shipped change. Append a normal Markdown
|
issue is the canonical task for the shipped change. Append a normal Markdown
|
||||||
link such as `([#55](https://github.com/Matysh/houseplan-card/issues/55))` to
|
link such as `([#55](https://github.com/Matysh/houseplan-card/issues/55))` to
|
||||||
@@ -712,13 +737,9 @@ behaviour change must spend at least one published beta/RC before stable. A
|
|||||||
stable release commit may change only version fields, generated bundle
|
stable release commit may change only version fields, generated bundle
|
||||||
snapshots and changelog/release metadata; feature source changes belong in the
|
snapshots and changelog/release metadata; feature source changes belong in the
|
||||||
preceding pre-release commit. Skip this step only for an explicit owner-approved
|
preceding pre-release commit. Skip this step only for an explicit owner-approved
|
||||||
emergency hotfix, and document the exception in the handoff.
|
emergency hotfix, and document the exception in the handoff. A
|
||||||
|
`Release vX.Y.Z-beta.N candidate` commit is **not** promotion-only: it carries
|
||||||
## Reproducible scripts (data)
|
the work itself and follows the ordinary rules, trailers included.
|
||||||
|
|
||||||
- Extracting the geometry/backgrounds from the prototype and generating `src/data/*` — see the commit
|
|
||||||
history and docs/ARCHITECTURE.md (SVG→base-space transforms: f1 0.647/(490,27), f2 0.896/(351,21)).
|
|
||||||
- Room fitting: render the plan with rectangles overlaid (cv2) → snap to walls → manual fine-tuning.
|
|
||||||
|
|
||||||
## Smoke tests (since 2026-07-27)
|
## Smoke tests (since 2026-07-27)
|
||||||
|
|
||||||
@@ -746,3 +767,15 @@ its physical resize after the Lit update (#460).
|
|||||||
|
|
||||||
When adding a checklist line marked `[auto: ...]` in docs/TESTING.md, add the
|
When adding a checklist line marked `[auto: ...]` in docs/TESTING.md, add the
|
||||||
failing check in the same commit — that is what the marker now promises.
|
failing check in the same commit — that is what the marker now promises.
|
||||||
|
|
||||||
|
The fake `hass` in `demo/srv/demo.html` is set once: opened directly in a
|
||||||
|
browser, the page renders the plan but **device icons appear only after a
|
||||||
|
re-render** (F5, or `card.hass = {...card.hass}`). `demo/serve.mjs` does that
|
||||||
|
nudge for smokes; a plain browser session does not. It is a harness limitation,
|
||||||
|
not a card bug.
|
||||||
|
|
||||||
|
Known environment-sensitive smoke: `demo/smoke_opening_measure.mjs` fails two
|
||||||
|
sub-checks (`place_dialog_x_magnetised`, `place_committed_x_center`) under the
|
||||||
|
pinned Chromium — a `1e-6`-tolerance magnet snap on the opening *placement*
|
||||||
|
path. It reproduces against the pristine committed bundle: pre-existing pixel
|
||||||
|
precision, not a regression of the change under test.
|
||||||
|
|||||||
+34
-86
@@ -1,21 +1,10 @@
|
|||||||
# Project status & session context
|
# Project status
|
||||||
|
|
||||||
> **Purpose of this file.** Cowork/AI sessions lose context (overflow, new session).
|
> The current state for a resuming session: a generated snapshot, the current
|
||||||
> This file is the **first thing to read** when resuming work. It captures the current
|
> cycle and the standing decisions that explain it. Rules are not here — the
|
||||||
> state, where everything lives, and how to continue safely.
|
> process is `PROCESS.md`, release mechanics are `docs/DEVELOPMENT.md` › Release,
|
||||||
>
|
> and task scope and status live in GitHub Issues and their labels. Update a row
|
||||||
> **Documentation policy (mandatory):** every change is documented *in the same
|
> in the same commit as the change it describes (`PROCESS.md` §2.6).
|
||||||
> commit* — a CHANGELOG entry for anything user-visible **in BOTH
|
|
||||||
> `docs/CHANGELOG.md` (English) and `docs/CHANGELOG.ru.md` (Russian, since
|
|
||||||
> v1.42.0 — the user base is largely Russian-speaking, see the Telegram chat)**, STATUS.md for state changes
|
|
||||||
> (versions, publication, infrastructure), DEVELOPMENT.md for new gotchas,
|
|
||||||
> ARCHITECTURE.md for design changes. Work scope and status live in GitHub
|
|
||||||
> Issues and their labels, not in a parallel backlog document.
|
|
||||||
|
|
||||||
**Promotion rule (2026-08-08):** every new feature or material behaviour
|
|
||||||
change must pass through a published beta/RC before stable. Stable release
|
|
||||||
commits are promotion-only (versions, generated bundles and release/changelog
|
|
||||||
metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
|
|
||||||
|
|
||||||
## Snapshot
|
## Snapshot
|
||||||
|
|
||||||
@@ -32,82 +21,41 @@ Everything computable from the tree and git; regenerate, never edit by hand
|
|||||||
| Tests | Node unit 3140 · pure backend 393 · HA-harness backend 302 · browser smokes 278 (`npm run inventory`) |
|
| Tests | Node unit 3140 · pure backend 393 · HA-harness backend 302 · browser smokes 278 (`npm run inventory`) |
|
||||||
<!-- status-snapshot:end -->
|
<!-- status-snapshot:end -->
|
||||||
|
|
||||||
## Standing state and decisions
|
## Current cycle and standing decisions
|
||||||
|
|
||||||
Prose kept by hand: decisions and the state they explain. Update a row in the
|
|
||||||
same commit as the change it describes.
|
|
||||||
|
|
||||||
| Item | State |
|
| Item | State |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Current local cycle | **Beta v1.78.0-beta.6 candidate** — refreshed on the exact integrated `dev` tree after seven S8 items landed. It makes stair drawing, selection, resizing, rotation and snapping follow the decor box contract (#676), and flushes deferred virtual-light and vacuum-trail state during Home Assistant shutdown (#655). The candidate also synchronizes the English user guide (#668), tightens CI scheduling and runner hygiene (#658), reduces browser mutation guards and records their policy (#659), removes private infrastructure details from public documentation (#677), and archives obsolete scripts and documents (#678). `main` remains on stable v1.77.0. |
|
| Current local cycle | **Beta v1.78.0-beta.6 candidate** — refreshed on the exact integrated `dev` tree after seven S8 items landed. It makes stair drawing, selection, resizing, rotation and snapping follow the decor box contract (#676), and flushes deferred virtual-light and vacuum-trail state during Home Assistant shutdown (#655). The candidate also synchronizes the English user guide (#668), tightens CI scheduling and runner hygiene (#658), reduces browser mutation guards and records their policy (#659), removes private infrastructure details from public documentation (#677), and archives obsolete scripts and documents (#678). `main` remains on stable v1.77.0. |
|
||||||
| 2.5D View | #89 Stage 1 ships in v1.63.0-beta.1, #122 Stage 2 in v1.64.0, #160 Stage 3 in v1.73.0-beta.1, #570/#583 Stage 4 on `dev`. #649 Stage 6 makes it public: the installation-wide General settings switch `settings.volumetric_view` (Display, third item) replaces the alpha entry, the header toggle and the phone-menu item; raised tiles with one floor-shadow layer, a soft sun wash instead of Flat wedges, user wall colours independent of the theme, furniture at the Flat line width. #651 keeps device/lock clusters rigid and independent of live zoom/pan. Flat remains default and byte-for-byte unchanged; editors and `houseplan-space-card` stay Flat. Acceptance frames: `docs/design/649-25d-stage6/ACCEPTANCE.md`. |
|
| Branches | `main` carries stable releases only; pre-release tags point at `dev`. Work lands on `dev`, which is equal to or ahead of `main`, never behind. |
|
||||||
| Workflow | Superseded 2026-08-12: the pre-1.62 rule of "local edits without tests or commits" is **dead** — since release 1.62 every product change follows `PROCESS.md` (issue in `S5-ready`+, branch `issue/<NN>-slug`, trailers on every commit, review pipeline; `AGENTS.md` is the summary). Release mechanics below remain current. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the stable release is produced by `release.yml` (`workflow_dispatch` on `main` with the tag) — the only publisher of installable assets since #540: gates on the exact SHA (Validate, Full Performance, E2E on the candidate commit), one build, `houseplan.zip` archived from the committed tree, `SHA256SUMS`, draft → publish → read-back verification; a release published by hand in the GitHub form is turned back into a draft and walked through the same path, and a re-dispatch on a public tag is a repair that adds only missing assets. Release bodies are short and bilingual (Russian first); every bullet links its GitHub issue (#NN) so the #328 rules stay machine-checkable. A STABLE body aggregates the changelog since the PREVIOUS STABLE release (never since the last beta): features/fixes described across the line's beta changelogs must appear, while bugs that were introduced and fixed strictly inside the beta line (never shipped in any stable) are excluded — draft with `npm run release:notes -- <tag>`, curate by hand, then `npm run release:notes -- <tag> --verify` must pass. `Мелкие исправления и улучшения` / `Small fixes and improvements` is allowed only when the range really contains user-visible work not itemised in the body; a single-issue hotfix ships without it (the verifier enforces this). Every body ends with separate links to the Russian and English changelogs. Open or partially delivered issues are never presented as shipped. Telegram announcements are sent only for stable releases; beta and RC publication is silent. `docs/RELEASE-NOTES.md` is the current canonical body instance; `npm run release:prerelease -- <tag> --issues=… --yes` is the primary local publication path and the manual `Publish prerelease` workflow is its GitHub-only equivalent once present on `main`. Nothing is copied to the home instance by hand |
|
| 2.5D View | Public since #649: the installation-wide General settings switch `settings.volumetric_view` (Display). Flat stays the default and byte-for-byte unchanged; editors and `houseplan-space-card` stay Flat. Canonical: `docs/ISOMETRIC.md`. |
|
||||||
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records; their labels carry priority and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used. `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. |
|
| Input support | Owner's rule since 2026-08-08: View and kiosk are fully supported and release-blocking on touch; the three editors are desktop-first, touch editing is best effort. Canonical: `docs/TOUCH-SUPPORT.md`. |
|
||||||
| CI | #541 replaces three incompatible meanings of “green” with one machine-verifiable Validate proof: candidate SHA/tree, run ID/attempt, requested checks, actually executed jobs and independently checked content-addressed reuse. Review, merge and release share the same closed state machine; a light green dispatch cannot hide a full red run, and a dispatch without six executed mutant jobs cannot authorize review or merge. #656 makes repeated release proofs fail closed: among compatible full runs on one SHA the newest decides, so a later full red blocks an older green while light, stale and cancelled runs are skipped. #573 makes the proof composite — product-tree identity, accepted golden overlay (tree, index hash, either a `Baseline-Reviewed` run or a `Baseline-Reviewed-Local` attestation) and the content key of every reusable job — and release consumers on the candidate checkout recompute and compare all of it; the accepted overlay is an input of `golden` only, so a baseline-only commit after a golden-red candidate reuses smoke, performance smoke, parity and backend, skips caught witnesses and re-runs golden alone. #641 permits a complete attested WSL/ext4 capture from a clean published SHA to replace the first expected-red artifact-transport run, while a full independent GitHub Validate on the accepted exact SHA remains mandatory. Prerelease publication requires a green full exact-SHA proof covering frontend/backend, smoke (including the #73 rAF frame sampler), golden, HACS/Hassfest and the short absolute-ceiling performance smoke. Obsolete same-ref Validate runs are cancelled. Full seven-sample base/candidate performance remains in `performance.yml` (`main` push excluding workflow/docs-only mirrors, weekly, manual); stable release assets fail closed unless Validate and Full Performance are green for the exact tagged SHA and the stable-only CDP compositor screencast finds no empty/black presented frame. |
|
| Localization | UI in en/ru/de/fr (`src/i18n/*.json`); German and French load lazily through the registry introduced by #62. |
|
||||||
| Local toolchain | #557 removes ambient-PATH claims from the owner's workstation: `scripts/windows-toolchain.ps1` keeps verified portable repository-pinned Node and a dedicated repository-pinned Python `.venv-ci` without changing system defaults; `toolchain:check` reports the current versions and exact executable/package/browser paths. #576 verifies the actual owner setup end to end: repeated Windows setup reuses the existing Node/Python/Chromium, the pinned small gate and pure backend subset are green, and repeated WSL `--verify` runs from an ext4 clone pass the real HA subset without skips and produce a Linux golden capture. The WSL entrypoint uses its own nvm + `.venv-ci`. #641 adds `golden:wsl:capture`: only the ext4 clone, clean named branch at its published remote SHA, pinned toolchain, current source fingerprint, complete matrix and witness floor can produce the self-hashed local passport; plain local capture remains diagnostic. The passport can source baseline review, but exact-SHA Linux CI remains the merge/release canon. |
|
| HACS and community | In the HACS default catalog since 2026-08-25 (hacs/default#9004): install is a plain HACS search, `houseplan.zip` is attached to stable tags. Support channel — Telegram chat https://t.me/ha_houseplan; GitHub Issues stay for bugs and features. |
|
||||||
| HACS | **In the default catalog since 2026-08-25** (hacs/default#9004 merged). Install = plain HACS search. `houseplan.zip` is attached to stable tags automatically (verified on v1.72.0); forum/4pda announcement still pending |
|
| Furniture | Top-view category/variant palette (#159, #593) from the MIT pack `assets/furniture/houseplan-0.4.1` (#606); plan art is lazy (#474), saved geometry and default dimensions never change with the pack. |
|
||||||
| Localization | UI en/ru/de (src/i18n/*.json), everything user-visible localized incl. kiosk popover; German is loaded lazily through the registry introduced by #62 |
|
| Vacuums | Live puck, server-side trails and fit calibration are shipped; Roomba is not covered. Canonical: `docs/VACUUM.md`. |
|
||||||
| Furniture | #159 replaces the flat ~30-item picker with a two-level category/variant palette; #593 raises it to 60 top-view symbols, all designer artwork. #606 derives corrected pack 0.4.1 from the reviewed 93-SVG MIT source pack 0.4.0: exercise is a visible category, bookshelf/shelf_floor art matches their names, and old cactus objects resolve without rewriting saved data. The active pack is `assets/furniture/houseplan-0.4.1`; plan art is lazy (#474), front-view menu art stays in the lazy editor graph, and saved geometry/default dimensions remain unchanged. |
|
| Demo stand | **https://demo.houseplan.tech** — public, login `demo`/`demo`, resets to a synthetic home every hour. **https://dev.houseplan.tech** — closed, auto-deploys the head of `dev`. The demo home and what the stand cannot show: `demo/stand/README.md`. |
|
||||||
| Tests | Four layers: Node unit (`npm test`: frontend pure modules + tooling policy), pure backend (`pytest tests_backend`, runs anywhere), HA-harness backend (same folder, CI only — uses repository-pinned Python plus pytest-homeassistant-custom-component), and browser smokes (`demo/smoke_*.mjs`, headless chromium). **Counts and runtime pins are not duplicated here** — they drift faster than release prose; run `npm run inventory` for current counts and `npm run toolchain:check` for the executable pins, or read them from the exact CI run |
|
| Privacy | Real-house plan sources and screenshots are gone from the tree; public images are generated from synthetic fixtures (`docs/images/screenshots.json`). Old images persist in git history and release archives — history is deliberately not rewritten, because that would break release tags and HACS installs. |
|
||||||
| Input support | Owner's rule since 2026-08-08: View and kiosk are fully supported and release-blocking on touch. All three editors are desktop-first; touch editing is best effort and may be awkward, reduced or absent when parity is expensive. `docs/TOUCH-SUPPORT.md` defines the non-negotiable safety floor and documentation/test rules |
|
|
||||||
| Vacuums | Live puck, server-side trails and fit calibration are shipped. The local v1.61 Stage 1 contract in docs/VACUUM.md adds explicit Dreame/XCME/Valetudo coverage, registry-less source selection, capability diagnostics, path-gap preservation and source-health warnings; #205 resumes one ended same-map run through an inclusive 30-minute station/pause grace. #209 renders current and previous trails through the same bounded 17.5 cm rounded-corner curve without changing stored points or gaps. Roomba remains Stage 2 |
|
|
||||||
| Demo stand | **https://demo.houseplan.tech** — public, login `demo`/`demo`, resets to a pristine synthetic home every hour. **https://dev.houseplan.tech** — closed (basic auth), auto-deploys the `dev` branch every 10 min. Since 2026-07-31 the stand covers most of the manual checklist: a scripted robot vacuum (`demo/stand/demo_robot` — Tasshack-shaped map sensor, serpentine run, pre-solved calibration, seeded server trail), Zigbee-style LQI template sensors, hand/auto-triggered leak+smoke alarms, an hvac_action climate marker and working script/scene/automation targets for tap-run. The demo home and what the stand cannot show: `demo/stand/README.md` |
|
|
||||||
| Community | **Telegram chat: https://t.me/ha_houseplan** (created 2026-07-27) — the primary user-facing support channel; GitHub issues stay for bugs/features. Link it from any new release notes and posts |
|
|
||||||
| Product scope | `docs/SCOPE.md` is the feature guard rail; `docs/TOUCH-SUPPORT.md` is the input-support contract — check both before accepting interaction work |
|
|
||||||
|
|
||||||
The feature surface since the 2026-07-17 snapshot and the early release
|
The feature surface and early milestones are described by the changelog and the
|
||||||
milestones are archived in [`legacy/docs/STATUS-FEATURES.md`](../legacy/docs/STATUS-FEATURES.md)
|
user guide; the former parallel list is archived in
|
||||||
(#634, archived by #678): the feature surface is described by the changelog and
|
[`legacy/docs/STATUS-FEATURES.md`](../legacy/docs/STATUS-FEATURES.md).
|
||||||
the user guide, not by a parallel list.
|
|
||||||
|
|
||||||
## Where things live
|
## Where the rest lives
|
||||||
|
|
||||||
- **Source of truth:** the git repo on GitHub — work lands on `dev`, stable releases on
|
- Scope and personas — `docs/SCOPE.md`; process, statuses and gates —
|
||||||
`main`. In a sandbox session clone it from GitHub.
|
`PROCESS.md` (role digests in `docs/process/`).
|
||||||
- **Owner's folder:** `houseplan-card-src/houseplan-card` is the author's tree and
|
- Release mechanics, CI proof semantics and publication — `docs/DEVELOPMENT.md` › Release.
|
||||||
`houseplan-card-src/hp-dev` the owner's worktree on `dev` (`AGENTS.md` › Working
|
- Local toolchain (Windows, WSL, the five-minute contour) — `docs/DEVELOPMENT.md`.
|
||||||
trees). The former file mirror `houseplan/houseplan-card/` is no longer maintained.
|
- Tests — `docs/TESTING.md`: Node unit, pure backend, HA-harness backend (CI or
|
||||||
- **Production config:** server-side on the HA instance, `.storage/houseplan.config` +
|
WSL only; it uses repository-pinned Python plus
|
||||||
`.storage/houseplan.layout` (backups `.bak-v1100` exist on the box).
|
pytest-homeassistant-custom-component) and browser smokes. Counts and runtime
|
||||||
|
pins are never copied into prose: `npm run inventory`, `npm run toolchain:check`.
|
||||||
|
|
||||||
## Open items / watchlist
|
## How to resume work in a fresh session
|
||||||
|
|
||||||
0. **Canonical backlog** — [GitHub Issues](https://github.com/Matysh/houseplan-card/issues)
|
1. Read by role, as `AGENTS.md` › Read this first lists it.
|
||||||
contain task scope and acceptance criteria; their **labels** carry priority
|
2. Clone `https://github.com/Matysh/houseplan-card` and run `npm ci` — it installs
|
||||||
and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used;
|
the hooks (`git config core.hooksPath` → `.githooks`).
|
||||||
the former local product plan was removed from the tree (#678, git history
|
|
||||||
keeps it) and must not be used as a backlog.
|
|
||||||
1. Privacy: legacy real-house plan sources (`assets/`) and screenshots were
|
|
||||||
removed from the current tree. Public documentation images are generated
|
|
||||||
from synthetic fixtures by the `Docs screenshots` workflow, accepted with `npm run docs:accept -- --reviewed`, and indexed in
|
|
||||||
`docs/images/screenshots.json`. Old images persist in git history and release
|
|
||||||
archives; history rewrite is deliberately not done because it would break
|
|
||||||
release tags and HACS installs.
|
|
||||||
2. Roadmap: phases 7–10 are DONE (v1.12.0 quality scale, v1.13.0 universality,
|
|
||||||
v1.13.1 distribution). Next candidates: measure backend coverage (>95% goal);
|
|
||||||
mypy strict.
|
|
||||||
3. The public-doc screenshot harness is versioned in `demo/docs/capture.mjs` and
|
|
||||||
reuses the production component plus deterministic golden fixtures.
|
|
||||||
|
|
||||||
## How to resume work in a fresh session (checklist)
|
|
||||||
|
|
||||||
1. Read by role, as `AGENTS.md` › Read this first lists it: author — `docs/SCOPE.md` →
|
|
||||||
`AGENTS.md` → `docs/process/AUTHOR.md` → this file; reviewer — `docs/SCOPE.md` →
|
|
||||||
`AGENTS.md` → `docs/process/REVIEWER.md`; pipeline, gates or process — through
|
|
||||||
`PROCESS.md`.
|
|
||||||
2. Clone `https://github.com/Matysh/houseplan-card` and run `npm ci` — it installs the
|
|
||||||
hooks (`git config core.hooksPath` → `.githooks`). On the owner's Windows machine use
|
|
||||||
`scripts/windows-toolchain.ps1`; in WSL, an ext4 clone and
|
|
||||||
`bash scripts/wsl-setup.sh --verify`.
|
|
||||||
3. Start a task from its packet: `node scripts/task-packet.mjs --issue NN`.
|
3. Start a task from its packet: `node scripts/task-packet.mjs --issue NN`.
|
||||||
4. Build only through `npm run build`; the task's local gate is `npm run gate:small`
|
4. The task's local gate is `npm run gate:small`; nothing is copied to a Home
|
||||||
(`AGENTS.md` › Gates).
|
Assistant instance by hand (`PROCESS.md` §12).
|
||||||
5. Nothing is copied to the home instance by hand (`PROCESS.md` §12): it updates
|
|
||||||
through HACS by tag, and the dev stand takes the head of `dev` from the
|
|
||||||
`dev-build` branch.
|
|
||||||
|
|
||||||
## Product scope
|
|
||||||
|
|
||||||
docs/SCOPE.md (fixed 2026-07-22) is the guard rail for all feature work: mission,
|
|
||||||
personas, jobs J1–J7, partial/out-of-scope lists, excess audit. Check it before
|
|
||||||
accepting or proposing any feature.
|
|
||||||
|
|||||||
+66
-30
@@ -163,37 +163,17 @@ mutant-jobs в доказательстве ревью (#541) не меняют
|
|||||||
node scripts/no-new-any.mjs # origin/dev...HEAD
|
node scripts/no-new-any.mjs # origin/dev...HEAD
|
||||||
node scripts/no-new-any.mjs --base origin/dev --head HEAD
|
node scripts/no-new-any.mjs --base origin/dev --head HEAD
|
||||||
node scripts/no-new-any.mjs --diff patch.diff # или `-` для stdin
|
node scripts/no-new-any.mjs --diff patch.diff # или `-` для stdin
|
||||||
|
node scripts/no-new-any.mjs --total # весь долг src/**
|
||||||
```
|
```
|
||||||
|
|
||||||
В `src/**` сейчас **1034 вхождения** явного `any` в 49 файлах — больше, чем
|
Правило — `PROCESS.md` §8 «Новый код не добавляет `any`»: судятся только
|
||||||
называл аудит (330), потому что монолит с тех пор разделился и его обвязка
|
добавленные строки, исключение — `// any-ok: <конкретная причина>` на той же
|
||||||
уехала в `houseplan-editor-runtime.ts`. Разовая замена такого объёма — месяц
|
строке, текст разбирает парсер TypeScript. Здесь — только механика: причина
|
||||||
риска ради нуля пользовательской ценности, поэтому долг снимается при плановом
|
короче 12 символов или из списка заглушек скрипта (`todo`, `hack`, `потом`…) не
|
||||||
извлечении подсистем (#425, прежний #34). Гейт держит приращение на нуле.
|
проходит; код, дословно перенесённый блоком в другой файл того же диапазона,
|
||||||
|
новым не считается (#592). В CI гейт вызывается в job `frontend`; её checkout
|
||||||
Что он судит: **только добавленные строки** диапазона. Существующий `any` на
|
получил полную историю без блобов, потому что diff-aware проверке нужен
|
||||||
нетронутой строке законен. Правка строки со старым `any` считается новой
|
диапазон, а содержимое старых ревизий — нет.
|
||||||
ответственностью — изменённая строка в диффе выглядит добавленной, и это
|
|
||||||
намеренно: тронул, значит либо типизируй, либо обоснуй.
|
|
||||||
|
|
||||||
Исключение объявляется на той же строке:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const raw = (event as any).detail; // any-ok: форма события HA не типизирована в @types
|
|
||||||
```
|
|
||||||
|
|
||||||
Голый `// any-ok`, пустая причина и шаблоны вроде `todo`, `hack`, `потом` не
|
|
||||||
проходят: причина обязана быть не короче 12 символов и не совпадать со списком
|
|
||||||
заглушек в скрипте.
|
|
||||||
|
|
||||||
Ложных срабатываний нет по построению, а не по старанию: текст разбирается
|
|
||||||
парсером TypeScript, и нарушением считается узел `AnyKeyword`. Слово «any» в
|
|
||||||
комментарии, в строковом литерале, в многострочном шаблоне `html` и в
|
|
||||||
идентификаторах `company`, `anyOf`, `manyRooms` таким узлом не является.
|
|
||||||
|
|
||||||
В CI гейт вызывается в job `frontend`; её checkout получил полную историю без
|
|
||||||
блобов, потому что diff-aware проверке нужен диапазон, а содержимое старых
|
|
||||||
ревизий — нет.
|
|
||||||
|
|
||||||
## Тестовый фасад и приватное состояние (#629)
|
## Тестовый фасад и приватное состояние (#629)
|
||||||
|
|
||||||
@@ -288,7 +268,7 @@ node scripts/pre-push-gate.mjs --max-smokes=3 --max-mutants=1
|
|||||||
Отставание от `dev` — предупреждение, а не провал набора: гейтом остаётся
|
Отставание от `dev` — предупреждение, а не провал набора: гейтом остаётся
|
||||||
конвейер, который приводит ветку сам (#257) и забыть не может. Смысл локальной
|
конвейер, который приводит ветку сам (#257) и забыть не может. Смысл локальной
|
||||||
проверки в другом: после любого ребейза разбор на ревью становится полным, а не
|
проверки в другом: после любого ребейза разбор на ревью становится полным, а не
|
||||||
по дельте (§7.2), а конфликт всё равно чинится на машине автора — дешевле
|
по дельте (PROCESS §2.10), а конфликт всё равно чинится на машине автора — дешевле
|
||||||
узнать об этом до пуша, чем из комментария через сорок минут (#364). Отключается
|
узнать об этом до пуша, чем из комментария через сорок минут (#364). Отключается
|
||||||
флагом `--no-rebase-check`. Замер на реальном
|
флагом `--no-rebase-check`. Замер на реальном
|
||||||
диапазоне (`953f675~1..953f675`, правка `src/houseplan-card.ts`): типы 5 с,
|
диапазоне (`953f675~1..953f675`, правка `src/houseplan-card.ts`): типы 5 с,
|
||||||
@@ -347,6 +327,31 @@ tsc, юниты, смоки и мутанты по диффу. Решение х
|
|||||||
Обойти, как и процессный гейт, можно через `git push --no-verify` — и тогда то же
|
Обойти, как и процессный гейт, можно через `git push --no-verify` — и тогда то же
|
||||||
самое найдёт Validate, уже после того как код окажется в `dev`.
|
самое найдёт Validate, уже после того как код окажется в `dev`.
|
||||||
|
|
||||||
|
## Браузерные проверки до ревью: свежесть бандла и где снимать кадры
|
||||||
|
|
||||||
|
- **Смоки из AC — локально до `S7-code-review`** (#151): `node
|
||||||
|
demo/smoke_<имя>.mjs`. Красный смок, доехавший до ревью, стоит цикла; на
|
||||||
|
своей машине — минуту (на #89 ошибка фикстуры прожила целый раунд ревью).
|
||||||
|
- **Свежесть бандла** (#236). Отпечаток, вшитый сборкой, покрывает `src/`,
|
||||||
|
конфигурацию Rollup и TypeScript и `package-lock.json`; бенчмарки, golden и
|
||||||
|
съёмка документации зовут `assertFreshDemoBundle` сами, смоки получают его из
|
||||||
|
`launch()` в `demo/serve.mjs`. Несовпадение — жёсткий отказ, а не
|
||||||
|
предупреждение: смок на несвежем бандле краснеет частично и читается как
|
||||||
|
дефект логики. `HP_ALLOW_STALE_BUNDLE=1` отключает проверку для отладки и
|
||||||
|
говорит об этом вслух.
|
||||||
|
- **Сверять и снимать — разные вещи** (#455). `golden:verify` — совещательный
|
||||||
|
и законный где угодно, Windows включительно: он сообщает разницу и ничего не
|
||||||
|
принимает. **Съёмка** кадров вне Linux отказывает ещё до запуска браузера —
|
||||||
|
`golden:capture` через `demo/golden/policy.mjs`, скриншоты документации через
|
||||||
|
`npm run docs:capture` (именно скрипт, не голый `node demo/docs/capture.mjs`:
|
||||||
|
правка скрипта съёмки сама обесценивает индекс скриншотов). Причина — физика,
|
||||||
|
а не политика: Windows растеризует текст через DirectWrite, и кадр байт в байт
|
||||||
|
не совпадёт ни с одним эталоном. Осознанный обход —
|
||||||
|
`HP_ALLOW_FOREIGN_CAPTURE="причина"`, причина уходит в вывод и манифест.
|
||||||
|
Принимаются эталоны только `npm run golden:accept -- --reviewed` по полному
|
||||||
|
артефакту (`demo/golden/README.md`); единственный локальный короткий путь —
|
||||||
|
`npm run docs:accept -- --identical` (раздел про версию в кадрах ниже).
|
||||||
|
|
||||||
## Manifest входов: какие job запускать и что хешировать (#492)
|
## Manifest входов: какие job запускать и что хешировать (#492)
|
||||||
|
|
||||||
Один модуль, `scripts/check-inputs.mjs`, объявляет каждую проверку Validate
|
Один модуль, `scripts/check-inputs.mjs`, объявляет каждую проверку Validate
|
||||||
@@ -461,6 +466,37 @@ golden отображаемая версия идёт через seam `displayVe
|
|||||||
создаёт непустой `panel-wide-view-light-en.png` и печатает длительность.
|
создаёт непустой `panel-wide-view-light-en.png` и печатает длительность.
|
||||||
Это ранняя обратная связь; независимый exact-SHA Validate остаётся каноном.
|
Это ранняя обратная связь; независимый exact-SHA Validate остаётся каноном.
|
||||||
|
|
||||||
|
## Backend quality gates (#42)
|
||||||
|
|
||||||
|
- `tests_backend/requirements.txt` is the single source of backend CI
|
||||||
|
dependencies; `validate.yml` and `mutation-gate.yml` install from it (#392;
|
||||||
|
#42 added ruff and mypy).
|
||||||
|
- `pyproject.toml` configures ruff (`E/F/B/I`, `E501` ignored by decision) and
|
||||||
|
strict mypy for a grow-only allowlist of pure modules;
|
||||||
|
`tests_backend/test_backend_quality.py` guards completeness. The CI typing step
|
||||||
|
derives its module list from that allowlist, refuses an empty list and is
|
||||||
|
guarded by a test plus the `typing-gate-stops-running` mutant — a configured
|
||||||
|
but unexecuted gate measures nothing.
|
||||||
|
- `test/backend-test-hygiene.test.mjs` refuses any write into `sys.modules` from
|
||||||
|
a backend test by the fact of the write, not its spelling (#398). Exemptions:
|
||||||
|
`conftest.py` (stub only when Home Assistant is absent) and `pure_imports.py`
|
||||||
|
(registers a module only for `exec_module`, then removes the whole
|
||||||
|
`custom_components` difference — proven by an executable test).
|
||||||
|
- The backend job measures branch coverage (pure + HA harness combined), fails
|
||||||
|
below `scripts/backend-coverage-baseline.txt` and refuses to run when the HA
|
||||||
|
harness would silently skip.
|
||||||
|
- The clean-runner `geometry_parity` job (#548) compiles only the TypeScript
|
||||||
|
graph rooted at `src/junction-limits.ts`, loads the production Python module
|
||||||
|
without Home Assistant and compares both over
|
||||||
|
`test/fixtures/junction-limits-parity.json`. It fails closed on missing
|
||||||
|
prerequisites, announces the number of executed scenarios and reuses a result
|
||||||
|
only when both mirrors, the fixture, toolchain pins and harness inputs are
|
||||||
|
byte-identical.
|
||||||
|
- The error-code scanner proves every emitted code (`send_error` literals,
|
||||||
|
exception class attributes, literal and variable-passed `MarkerControlError`
|
||||||
|
codes, f-string families) is in `const.ERROR_CODES` / `ERROR_CODE_FAMILIES` and
|
||||||
|
localized.
|
||||||
|
|
||||||
## E2E на реальном Home Assistant (#514)
|
## E2E на реальном Home Assistant (#514)
|
||||||
|
|
||||||
Репозиторий `Matysh/houseplan-e2e`: настоящий HA в docker, House Plan из
|
Репозиторий `Matysh/houseplan-e2e`: настоящий HA в docker, House Plan из
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
целевые смоки, `no-new-any`; по диффу — `golden:verify`, `check-docs`,
|
целевые смоки, `no-new-any`; по диффу — `golden:verify`, `check-docs`,
|
||||||
`model-invariants`, `pytest tests_backend`, junction parity. Команды —
|
`model-invariants`, `pytest tests_backend`, junction parity. Команды —
|
||||||
в каноне ([§8](../../PROCESS.md#8-гейты)); `npm run gate:small` собирает
|
в каноне ([§8](../../PROCESS.md#8-гейты)); `npm run gate:small` собирает
|
||||||
обязательную часть (`AGENTS.md`, «Gates»).
|
обязательную часть (`docs/TESTING.md`, «Локальный набор перед пушем»).
|
||||||
- Бандл в коммит задачи не идёт: сборка переписывает отслеживаемый `dist/`,
|
- Бандл в коммит задачи не идёт: сборка переписывает отслеживаемый `dist/`,
|
||||||
перед коммитом — `npm run bundle:clean`; хук `commit-msg` отклоняет пути
|
перед коммитом — `npm run bundle:clean`; хук `commit-msg` отклоняет пути
|
||||||
бандла без трейлера `Release:` (#657, [§1](../../PROCESS.md#1-основное-правило)).
|
бандла без трейлера `Release:` (#657, [§1](../../PROCESS.md#1-основное-правило)).
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
- Условие честности сужения: ревьюер обязан перечислить, какие гейты прогнал,
|
- Условие честности сужения: ревьюер обязан перечислить, какие гейты прогнал,
|
||||||
какие нет и почему ([§8](../../PROCESS.md#8-гейты)).
|
какие нет и почему ([§8](../../PROCESS.md#8-гейты)).
|
||||||
- Зелёный `pytest tests_backend` без Home Assistant скипает `test_ha_*.py` и
|
- Зелёный `pytest tests_backend` без Home Assistant скипает `test_ha_*.py` и
|
||||||
ничего не доказывает — это «чего не проверял» (`AGENTS.md`, «Gates»;
|
ничего не доказывает — это «чего не проверял» (`docs/TESTING.md`;
|
||||||
[§8](../../PROCESS.md#8-гейты)).
|
[§8](../../PROCESS.md#8-гейты)).
|
||||||
|
|
||||||
## Повторный раунд
|
## Повторный раунд
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function rebaseAdvice({ behind, base = 'origin/dev' }) {
|
|||||||
const commits = count === 1 ? 'коммит' : count < 5 ? 'коммита' : 'коммитов';
|
const commits = count === 1 ? 'коммит' : count < 5 ? 'коммита' : 'коммитов';
|
||||||
return `ветка отстала от ${base} на ${count} ${commits}.`
|
return `ветка отстала от ${base} на ${count} ${commits}.`
|
||||||
+ ` Конвейер приведёт её сам перед ревью, но после ребейза разбор станет`
|
+ ` Конвейер приведёт её сам перед ревью, но после ребейза разбор станет`
|
||||||
+ ` полным, а не по дельте (§7.2). Дешевле сделать это здесь:`
|
+ ` полным, а не по дельте (§2.10). Дешевле сделать это здесь:`
|
||||||
+ ` git fetch origin && git rebase ${base}`;
|
+ ` git fetch origin && git rebase ${base}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,5 +46,5 @@ export function devMovedNote({ moved, sha }) {
|
|||||||
const at = sha ? ` Материал ревью — \`${sha}\`.` : '';
|
const at = sha ? ` Материал ревью — \`${sha}\`.` : '';
|
||||||
return `Пока шло ревью, \`dev\` продвинулся на ${count} ${commits}.${at}`
|
return `Пока шло ревью, \`dev\` продвинулся на ${count} ${commits}.${at}`
|
||||||
+ ' Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии:'
|
+ ' Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии:'
|
||||||
+ ' слияние приведёт ветку к dev, и это другой код (§7.2).';
|
+ ' слияние приведёт ветку к dev, и это другой код (§2.10).';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function commentFor(action, ctx) {
|
|||||||
+ `1. \`git fetch origin\`, затем \`git rebase origin/dev\` в ветке задачи, разрешить конфликт;\n2. запушить ветку;\n3. вернуть метку \`S7-code-review\`.\n\n`
|
+ `1. \`git fetch origin\`, затем \`git rebase origin/dev\` в ветке задачи, разрешить конфликт;\n2. запушить ветку;\n3. вернуть метку \`S7-code-review\`.\n\n`
|
||||||
+ `Повторный прогон ревью — не формальность: после ребейза на новый \`dev\` это другой код, и принимать его без проверки нельзя. Цикл считается по этапу, лимит на код-ревью тратится отдельно от ревью ТЗ.`;
|
+ `Повторный прогон ревью — не формальность: после ребейза на новый \`dev\` это другой код, и принимать его без проверки нельзя. Цикл считается по этапу, лимит на код-ревью тратится отдельно от ревью ТЗ.`;
|
||||||
case 'rereview':
|
case 'rereview':
|
||||||
return `**Дифф изменился при ребейзе на \`dev@${short(ctx.devNow)}\` — вердикт к нему не применим (§7.2, #492).**\n\n`
|
return `**Дифф изменился при ребейзе на \`dev@${short(ctx.devNow)}\` — вердикт к нему не применим (§2.10, #492).**\n\n`
|
||||||
+ `Материал ревью \`${short(ctx.material)}\` и кандидат \`${short(ctx.candidate)}\` дают разные patch-id: соседние правки в \`dev\` изменили содержимое патча. Кандидат опубликован в ветку; задача возвращена в \`S7-code-review\` — новый заход ревью читает актуальный код.`;
|
+ `Материал ревью \`${short(ctx.material)}\` и кандидат \`${short(ctx.candidate)}\` дают разные patch-id: соседние правки в \`dev\` изменили содержимое патча. Кандидат опубликован в ветку; задача возвращена в \`S7-code-review\` — новый заход ревью читает актуальный код.`;
|
||||||
case 'validation-red':
|
case 'validation-red':
|
||||||
return `**Кандидат после ребейза на \`dev@${short(ctx.devNow)}\` красный (#492).**\n\n`
|
return `**Кандидат после ребейза на \`dev@${short(ctx.devNow)}\` красный (#492).**\n\n`
|
||||||
|
|||||||
+42
-7
@@ -5,11 +5,13 @@
|
|||||||
* node scripts/no-new-any.mjs # origin/dev...HEAD
|
* node scripts/no-new-any.mjs # origin/dev...HEAD
|
||||||
* node scripts/no-new-any.mjs --base origin/dev --head HEAD
|
* node scripts/no-new-any.mjs --base origin/dev --head HEAD
|
||||||
* node scripts/no-new-any.mjs --diff patch.diff # или `-` для stdin
|
* node scripts/no-new-any.mjs --diff patch.diff # или `-` для stdin
|
||||||
|
* node scripts/no-new-any.mjs --total # весь долг src/** (#680)
|
||||||
*
|
*
|
||||||
* Зачем гейт, а не разовая типизация. В `src/**` сейчас 1034 вхождения явного
|
* Зачем гейт, а не разовая типизация. Явного `any` в `src/**` — сотни
|
||||||
* `any` в 49 файлах — перетипизировать это одним заходом значит месяц риска ради
|
* вхождений (точное число печатает `--total`); перетипизировать это одним
|
||||||
* нуля пользовательской ценности. Долг снимается при плановом извлечении
|
* заходом значит месяц риска ради нуля пользовательской ценности. Долг
|
||||||
* подсистем (#425, прежний #34). Задача гейта одна: не давать долгу расти.
|
* снимается при плановом извлечении подсистем (#425, прежний #34). Задача
|
||||||
|
* гейта одна: не давать долгу расти.
|
||||||
*
|
*
|
||||||
* Практический вред уже случался: несоответствие форм (`d.source.kind` против
|
* Практический вред уже случался: несоответствие форм (`d.source.kind` против
|
||||||
* строкового `source`) компилятор не поймал, потому что путь был через `any`, и
|
* строкового `source`) компилятор не поймал, потому что путь был через `any`, и
|
||||||
@@ -51,8 +53,8 @@
|
|||||||
* добавление: повторная вставка того же блока остаётся новым кодом.
|
* добавление: повторная вставка того же блока остаётся новым кодом.
|
||||||
*/
|
*/
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { existsSync, readFileSync } from 'node:fs';
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
||||||
import { dirname, resolve } from 'node:path';
|
import { dirname, join, relative, resolve } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import ts from 'typescript';
|
import ts from 'typescript';
|
||||||
|
|
||||||
@@ -103,6 +105,34 @@ export function anyKeywordLines(path, text) {
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Весь существующий долг (#680): число узлов `AnyKeyword` и файлов с ними по
|
||||||
|
* продуктовому TypeScript. Документы называют эту команду вместо числа — число в
|
||||||
|
* прозе отставало от дерева с первой недели.
|
||||||
|
*/
|
||||||
|
export function totalAny(files) {
|
||||||
|
let occurrences = 0;
|
||||||
|
let withAny = 0;
|
||||||
|
for (const file of files) {
|
||||||
|
let count = 0;
|
||||||
|
for (const perLine of anyKeywordLines(file.path, file.text).values()) count += perLine;
|
||||||
|
occurrences += count;
|
||||||
|
if (count) withAny += 1;
|
||||||
|
}
|
||||||
|
return { occurrences, files: withAny };
|
||||||
|
}
|
||||||
|
|
||||||
|
function productTypeScriptFiles(dir = join(ROOT, 'src')) {
|
||||||
|
const out = [];
|
||||||
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const full = join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) { out.push(...productTypeScriptFiles(full)); continue; }
|
||||||
|
const path = relative(ROOT, full).split('\\').join('/');
|
||||||
|
if (isProductTypeScript(path)) out.push({ path, text: readFileSync(full, 'utf8') });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Нарушения: `any` на строке, которую диапазон объявил добавленной.
|
* Нарушения: `any` на строке, которую диапазон объявил добавленной.
|
||||||
*
|
*
|
||||||
@@ -303,6 +333,11 @@ function main(argv) {
|
|||||||
return index >= 0 && argv[index + 1] && !argv[index + 1].startsWith('--')
|
return index >= 0 && argv[index + 1] && !argv[index + 1].startsWith('--')
|
||||||
? argv[index + 1] : fallback;
|
? argv[index + 1] : fallback;
|
||||||
};
|
};
|
||||||
|
if (argv.includes('--total')) {
|
||||||
|
const total = totalAny(productTypeScriptFiles());
|
||||||
|
console.log(`Явный any в src/**/*.ts: ${total.occurrences} вхождений в ${total.files} файл(ах).`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
const diffArg = value('diff');
|
const diffArg = value('diff');
|
||||||
let diff;
|
let diff;
|
||||||
if (diffArg) {
|
if (diffArg) {
|
||||||
@@ -352,7 +387,7 @@ function main(argv) {
|
|||||||
console.error('\nЛибо типизируйте, либо обоснуйте на той же строке:');
|
console.error('\nЛибо типизируйте, либо обоснуйте на той же строке:');
|
||||||
console.error(' // any-ok: <конкретная причина, почему тип недоступен>');
|
console.error(' // any-ok: <конкретная причина, почему тип недоступен>');
|
||||||
console.error('Существующий долг снимается при извлечении подсистем (#34, #342),');
|
console.error('Существующий долг снимается при извлечении подсистем (#34, #342),');
|
||||||
console.error('а не разовой заменой: в src/** его 1034 вхождения в 49 файлах.');
|
console.error('а не разовой заменой; сколько его — node scripts/no-new-any.mjs --total.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ function manualGate(argv) {
|
|||||||
|
|
||||||
// ---- приведена ли ветка к dev ---------------------------------------------
|
// ---- приведена ли ветка к dev ---------------------------------------------
|
||||||
// Конвейер ребейзит сам (#257), но после ребейза разбор становится полным, а не
|
// Конвейер ребейзит сам (#257), но после ребейза разбор становится полным, а не
|
||||||
// по дельте (§7.2), и конфликт всплывает в комментарии через сорок минут вместо
|
// по дельте (§2.10), и конфликт всплывает в комментарии через сорок минут вместо
|
||||||
// машины автора. Поэтому предупреждение, а не гейт: гейтом остаётся конвейер.
|
// машины автора. Поэтому предупреждение, а не гейт: гейтом остаётся конвейер.
|
||||||
if (!flag('no-rebase-check')) {
|
if (!flag('no-rebase-check')) {
|
||||||
const fetched = capture('git', ['fetch', '-q', 'origin', base.replace(/^origin\//, '')]);
|
const fetched = capture('git', ['fetch', '-q', 'origin', base.replace(/^origin\//, '')]);
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
// Решения, принятые при реализации. Продуктового поведения не касаются, поэтому
|
// Решения, принятые при реализации. Продуктового поведения не касаются, поэтому
|
||||||
// приняты здесь, а не у владельца; ревьюер вправе оспорить любое.
|
// приняты здесь, а не у владельца; ревьюер вправе оспорить любое.
|
||||||
//
|
//
|
||||||
// 1. «Release vX.Y.Z-beta.N candidate» — НЕ релизный коммит. Promotion-only по
|
// 1. «Release vX.Y.Z-beta.N candidate» — НЕ релизный коммит. Promotion-only
|
||||||
// AGENTS.md — это стабильный релиз; кандидат беты несёт саму работу и живёт
|
// (`PROCESS.md` §3 п.16, `docs/DEVELOPMENT.md` › Release) — это стабильный релиз; кандидат беты несёт саму работу и живёт
|
||||||
// по общим правилам, включая трейлер Issue.
|
// по общим правилам, включая трейлер Issue.
|
||||||
// 2. Классы дополнены: package.json, package-lock.json, pytest.ini, .gitignore,
|
// 2. Классы дополнены: package.json, package-lock.json, pytest.ini, .gitignore,
|
||||||
// .gitattributes, .githooks/** — класс B (конфигурация сборки и гейтов).
|
// .gitattributes, .githooks/** — класс B (конфигурация сборки и гейтов).
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export function materialAnchorsFrom(text) {
|
|||||||
* issue. Коммита с таким именем в репозитории нет и не было: клон не мелкий,
|
* issue. Коммита с таким именем в репозитории нет и не было: клон не мелкий,
|
||||||
* `git rev-list --all` его не знает. Скорее всего значение снято до `amend`
|
* `git rev-list --all` его не знает. Скорее всего значение снято до `amend`
|
||||||
* или `rebase` при публикации — то есть проверка `git rev-parse HEAD` перед
|
* или `rebase` при публикации — то есть проверка `git rev-parse HEAD` перед
|
||||||
* выводом отчёта, которую требует §7.2, не выполнялась ни у автора, ни у
|
* выводом отчёта, которую требует §2.7, не выполнялась ни у автора, ни у
|
||||||
* ревьюера.
|
* ревьюера.
|
||||||
*
|
*
|
||||||
* Цена уже заплачена на следующем раунде: пункт «найти SHA, на котором получен
|
* Цена уже заплачена на следующем раунде: пункт «найти SHA, на котором получен
|
||||||
@@ -182,7 +182,7 @@ export function danglingMaterialRefusal(
|
|||||||
+ 'Команда `git diff <sha>..HEAD` из PROCESS.md §2.10 на таком отчёте не'
|
+ 'Команда `git diff <sha>..HEAD` из PROCESS.md §2.10 на таком отчёте не'
|
||||||
+ ' работает, а следующий раунд восстанавливает коммит по содержимому'
|
+ ' работает, а следующий раунд восстанавливает коммит по содержимому'
|
||||||
+ ' диффа руками (#413). Сверьте SHA командой `git rev-parse HEAD`'
|
+ ' диффа руками (#413). Сверьте SHA командой `git rev-parse HEAD`'
|
||||||
+ ' непосредственно перед выводом отчёта — §7.2 требует именно этого,'
|
+ ' непосредственно перед выводом отчёта — §2.7 требует именно этого,'
|
||||||
+ ' а не значения, записанного до amend или rebase.';
|
+ ' а не значения, записанного до amend или rebase.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +531,7 @@ export function anchorVerdictFrom(text) {
|
|||||||
* - текущее дерево отличается от якоря НИЧЕМ, кроме docs/reviews/** —
|
* - текущее дерево отличается от якоря НИЧЕМ, кроме docs/reviews/** —
|
||||||
* сравнение делает git по содержимому, так что ребейз на ушедший dev,
|
* сравнение делает git по содержимому, так что ребейз на ушедший dev,
|
||||||
* правка теста, фикстуры, скрипта или ТЗ в docs/specs дают отличие и
|
* правка теста, фикстуры, скрипта или ТЗ в docs/specs дают отличие и
|
||||||
* полный разбор (§7.2). Смена базы без изменения дерева невозможна:
|
* полный разбор (§2.10). Смена базы без изменения дерева невозможна:
|
||||||
* дерево ветки после ребейза включает содержимое dev.
|
* дерево ветки после ребейза включает содержимое dev.
|
||||||
* Любое сомнение — `null`, и ревью идёт как обычно.
|
* Любое сомнение — `null`, и ревью идёт как обычно.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { conflictingPaths, devMovedNote, rebaseAdvice } from '../scripts/branch-
|
|||||||
// #364. Конвейер приводит ветку к dev сам (#257) и при конфликте возвращает
|
// #364. Конвейер приводит ветку к dev сам (#257) и при конфликте возвращает
|
||||||
// задачу, не тратя цикл ревью. Но конфликт всплывает в комментарии через сорок
|
// задачу, не тратя цикл ревью. Но конфликт всплывает в комментарии через сорок
|
||||||
// минут, а чинится на машине автора; и после любого ребейза разбор становится
|
// минут, а чинится на машине автора; и после любого ребейза разбор становится
|
||||||
// полным, а не по дельте (§7.2). Эти helpers переносят обнаружение туда, где
|
// полным, а не по дельте (§2.10). Эти helpers переносят обнаружение туда, где
|
||||||
// есть руки, и делают возврат адресным.
|
// есть руки, и делают возврат адресным.
|
||||||
|
|
||||||
test('приведённая ветка совета не требует (#364)', () => {
|
test('приведённая ветка совета не требует (#364)', () => {
|
||||||
@@ -46,5 +46,5 @@ test('уход dev во время ревью описывается тольк
|
|||||||
assert.match(note, /продвинулся на 2 коммита/);
|
assert.match(note, /продвинулся на 2 коммита/);
|
||||||
assert.match(note, /`abc1234`/);
|
assert.match(note, /`abc1234`/);
|
||||||
// Вывод, ради которого строка и нужна: вердикт вынесен по другому дереву.
|
// Вывод, ради которого строка и нужна: вердикт вынесен по другому дереву.
|
||||||
assert.match(note, /§7\.2/);
|
assert.match(note, /§2\.10/);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import assert from 'node:assert/strict';
|
|||||||
import {
|
import {
|
||||||
MOVED_BLOCK_MIN,
|
MOVED_BLOCK_MIN,
|
||||||
addedLinesByFile, anyKeywordLines, blameLine, findNewAnyViolations, formatViolation,
|
addedLinesByFile, anyKeywordLines, blameLine, findNewAnyViolations, formatViolation,
|
||||||
movedLinesByFile, parseAnyOk,
|
movedLinesByFile, parseAnyOk, totalAny,
|
||||||
} from '../scripts/no-new-any.mjs';
|
} from '../scripts/no-new-any.mjs';
|
||||||
|
|
||||||
// #342. Цель гейта — не перетипизировать монолит, а не давать долгу расти. В
|
// #342. Цель гейта — не перетипизировать монолит, а не давать долгу расти. В
|
||||||
// src/** сейчас 1034 вхождения явного any в 49 файлах; разовая замена — месяц
|
// src/** сотни вхождений явного any (`node scripts/no-new-any.mjs --total`);
|
||||||
// риска ради нуля пользовательской ценности, поэтому долг снимается при
|
// разовая замена — месяц риска ради нуля пользовательской ценности, поэтому
|
||||||
// извлечении подсистем (#34), а гейт держит приращение на нуле.
|
// долг снимается при извлечении подсистем (#34), а гейт держит приращение на нуле.
|
||||||
|
|
||||||
const file = (text, addedLines) => ({
|
const file = (text, addedLines) => ({
|
||||||
path: 'src/probe.ts', text, addedLines: new Set(addedLines),
|
path: 'src/probe.ts', text, addedLines: new Set(addedLines),
|
||||||
@@ -241,3 +241,12 @@ test('#592 перенос с изменённым отступом перено
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
assert.equal(movedLinesByFile(diff).size, 0);
|
assert.equal(movedLinesByFile(diff).size, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#680 --total: считает узлы AnyKeyword и файлы с ними, не слово any в прозе', () => {
|
||||||
|
const total = totalAny([
|
||||||
|
{ path: 'src/a.ts', text: 'let a: any; const b = x as any; // any в комментарии\n' },
|
||||||
|
{ path: 'src/b.ts', text: 'const company = "any"; function f(v: unknown) { return v; }\n' },
|
||||||
|
{ path: 'src/c.ts', text: 'type T = Record<string, any>;\n' },
|
||||||
|
]);
|
||||||
|
assert.deepEqual(total, { occurrences: 3, files: 2 });
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user