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"
|
||||
short_before=$(git rev-parse --short "$before")
|
||||
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"
|
||||
|
||||
# Материал ревью — конкретный SHA (#312). Вердикт применим только к
|
||||
@@ -1015,7 +1015,8 @@ jobs:
|
||||
файла в материале нет — читай PROCESS.md §2.4, §2.7, §2.10,
|
||||
§4, §7.2, §8, §12. Задача правит сам конвейер, гейты или
|
||||
процесс — PROCESS.md целиком, §10 в первую очередь.
|
||||
3. AGENTS.md — классы изменений, трейлеры, гейты, формат вердикта.
|
||||
3. AGENTS.md — карта пакета, правило №1, классы изменений, треки,
|
||||
трейлеры и ожидание вердикта; сами правила — по его ссылкам.
|
||||
4. Тело issue #${{ github.event.issue.number }} и все комментарии.
|
||||
5. Если меняется видимое поведение — docs/USER-GUIDE.ru.md:
|
||||
терминология интерфейса берётся оттуда, а не изобретается.
|
||||
@@ -1538,7 +1539,7 @@ jobs:
|
||||
# Ревью идёт десятки минут, а dev за это время двигается (28 августа —
|
||||
# четыре раза за день). Вердикт при этом вынесен по дереву, которое уже не
|
||||
# совпадает с вершиной линии, и слияние приведёт ветку к dev — то есть в
|
||||
# dev уедет код, отличный от прочитанного (§7.2). Молчать об этом нельзя,
|
||||
# dev уедет код, отличный от прочитанного (§2.10). Молчать об этом нельзя,
|
||||
# но и шуметь на каждом прогоне ни к чему: строка появляется только когда
|
||||
# dev действительно ушёл и вердикт зелёный, то есть слияние вот-вот
|
||||
# случится (#364).
|
||||
@@ -1557,7 +1558,7 @@ jobs:
|
||||
if [ "$moved" -eq 0 ] || [ "$GREEN" != "true" ]; then exit 0; fi
|
||||
short=$(git rev-parse --short "$MATERIAL")
|
||||
gh issue comment "$NUM" --repo "${{ github.repository }}" --body \
|
||||
"Пока шло ревью, \`dev\` продвинулся на $moved коммит(ов). Материал ревью — \`$short\`. Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии: слияние приведёт ветку к dev, и это другой код (§7.2)."
|
||||
"Пока шло ревью, \`dev\` продвинулся на $moved коммит(ов). Материал ревью — \`$short\`. Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии: слияние приведёт ветку к dev, и это другой код (§2.10)."
|
||||
|
||||
# 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
|
||||
the entry, manifest and hashed chunks under `dist/`.
|
||||
- **Storage integration** (`custom_components/houseplan/`, Python) — the Home Assistant backend.
|
||||
- **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/`.
|
||||
- **Storage integration** (`custom_components/houseplan/`, Python) — the Home
|
||||
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
|
||||
|
||||
**`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
|
||||
serve a job listed there. It carries the mission, the three personas, the core
|
||||
user jobs and the out-of-scope list.
|
||||
|
||||
Its central consequence: **View mode is the product 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 `docs/USER-GUIDE.ru.md` —
|
||||
interface wording comes from there and is not invented, or the UI starts speaking
|
||||
developer.
|
||||
serve a job listed there. Its central consequence: **View mode is the product
|
||||
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
|
||||
`docs/USER-GUIDE.ru.md` — interface wording comes from there and is not invented.
|
||||
|
||||
**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:
|
||||
@@ -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` →
|
||||
`AGENTS.md` → `PROCESS.md` → `docs/STATUS.md`.
|
||||
|
||||
The two digests quote and link `PROCESS.md` section by section; it stays the
|
||||
only complete canon and wins any disagreement, so open the linked section
|
||||
whenever a digest line governs your current step. For non-trivial changes add
|
||||
`docs/ARCHITECTURE.md` plus the canonical document of the subsystem you touch
|
||||
(one list, the same one the reviewer prompt in `_process.yml` reads):
|
||||
The two digests quote and link `PROCESS.md` section by section; open the linked
|
||||
section whenever a digest line governs your current step. For non-trivial
|
||||
changes add `docs/ARCHITECTURE.md` plus the canonical document of the subsystem
|
||||
you touch (one list, the same one the reviewer prompt in `_process.yml` reads):
|
||||
`SUN.md`, `LIGHT.md`, `CANVAS.md`, `WALL-THICKNESS.md`, `UX-MODES.md`,
|
||||
`CONFIG-COMPATIBILITY.md`, `TOUCH-SUPPORT.md`, `ISOMETRIC.md`, `VACUUM.md`,
|
||||
`DECOR-EDITOR.md`, `DEVICE-PRESENTATION.md`, `FILTERING.md`, `STAIRS.md`,
|
||||
`RADAR.md`, `PDF-EXPORT.md`, `STYLING-HOOKS.md`.
|
||||
|
||||
Standard commands live in `package.json` scripts, `CONTRIBUTING.md` and
|
||||
`docs/DEVELOPMENT.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.
|
||||
Where the rest lives: commands — `package.json` scripts, `CONTRIBUTING.md`,
|
||||
`docs/DEVELOPMENT.md` (toolchain, build, release — its Release section is the
|
||||
only home of release mechanics); tests and gates — `docs/TESTING.md`.
|
||||
|
||||
## 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.
|
||||
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 |
|
||||
|---|---|---|
|
||||
| **A — product** | `src/**`, `custom_components/houseplan/**/*.py`, `manifest.json`, `hacs.json`, i18n, `custom_components/**/translations/**` | yes |
|
||||
| **B — gates and tooling** | `test/**`, `tests_backend/**`, `demo/**`, `scripts/**`, `.github/workflows/**`, `rollup.config.mjs`, `tsconfig*.json` | yes; may reuse the issue it covers |
|
||||
| **C — documentation** | `docs/**`, `README*`, `CHANGELOG*`, `AGENTS.md` | not if it is part of its issue's DoD |
|
||||
| **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 |
|
||||
**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
|
||||
`small` criterion the task fails. `trivial` skips the spec stage for a bug whose
|
||||
expected behaviour is already on record. An **infrastructure** task — not a single
|
||||
class A file — skips analysis and spec and enters at `S7-code-review`
|
||||
(`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
|
||||
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.
|
||||
## Specs
|
||||
|
||||
## 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
|
||||
scripts/install-hooks.mjs"`, so `npm ci` sets `core.hooksPath` in every fresh
|
||||
clone. Verify with `git config core.hooksPath` — expect `.githooks`.
|
||||
## Commits and branches
|
||||
|
||||
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
|
||||
Issue: #123
|
||||
User-Visible: yes
|
||||
```
|
||||
|
||||
One `Issue:` line per issue if a commit closes several. `User-Visible: no` for
|
||||
tests, refactors, tooling and documentation that does not change the product.
|
||||
`User-Visible: yes` requires edits to **both** changelogs — `docs/CHANGELOG.md`
|
||||
and `docs/CHANGELOG.ru.md` — in the same commit.
|
||||
One `Issue:` line per issue. `User-Visible: yes` requires edits to **both**
|
||||
`docs/CHANGELOG.md` and `docs/CHANGELOG.ru.md` in the same commit. A commit
|
||||
touching `demo/golden/baselines/**` also needs `Release:` plus exactly one of
|
||||
`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
|
||||
Release: v1.62.0-beta.9
|
||||
Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/<run-id>
|
||||
```
|
||||
|
||||
or, for a complete attested capture made in the repository's WSL/ext4 clone:
|
||||
|
||||
```text
|
||||
Release: v1.62.0-beta.9
|
||||
Baseline-Reviewed-Local: sha256:<wsl-attestation hash>
|
||||
```
|
||||
|
||||
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.
|
||||
- **Push after every task, not before a beta** — while work sits unpushed there
|
||||
is nothing to review.
|
||||
- **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.
|
||||
- **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
|
||||
`S6-in-progress` with the verdict intact; `node scripts/rebase-on-dev.mjs`
|
||||
resolves conflicts in generated files only (`PROCESS.md` §2.10), then push and
|
||||
re-apply `S7-code-review`.
|
||||
- Everything else needs the owner's explicit command: pushing `main`, tags,
|
||||
publishing betas and releases, closing issues.
|
||||
|
||||
## Working trees (#115)
|
||||
|
||||
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
|
||||
way. The layout is therefore fixed:
|
||||
branch. `houseplan-card-src/houseplan-card` is the author's tree — task branches
|
||||
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
|
||||
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.
|
||||
## Handoff and the verdict
|
||||
|
||||
A worktree is only usable on the machine that created it: the `.git` file records
|
||||
an absolute path in that machine's format. One created from a Linux sandbox is
|
||||
dead on Windows and vice versa — create worktrees on the machine that will use
|
||||
them, which for `hp-dev` means the owner's.
|
||||
Start a task from its packet: `node scripts/task-packet.mjs --issue NN` (status,
|
||||
track, what the status permits, the branch against `dev`, the previous verdict
|
||||
and the unwitnessed AC; it writes nothing). The local gate is
|
||||
`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
|
||||
may take any task**: analysis, spec, product implementation, infrastructure or a
|
||||
release explicitly commanded by the owner. Roles describe the current artifact,
|
||||
not the agent brand. The owner rules on product disputes, closes issues and
|
||||
commands releases.
|
||||
|
||||
Author and reviewer are independent agents/sessions. They need not use different
|
||||
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:
|
||||
**Having applied `S4-spec-review` or `S7-code-review`, wait for the result
|
||||
instead of ending the session.** The label starts the pipeline by itself. Poll
|
||||
with `node scripts/wait-verdict.mjs --issue NN [--sha <tip>]` (#496): it watches
|
||||
the label and the pipeline's comments every 90 s, at most 110 times, prints only
|
||||
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
|
||||
set.
|
||||
|
||||
| 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 |
|
||||
|
||||
**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
|
||||
snapshot every thirty minutes and exits. It may re-apply the same review label
|
||||
only when the matching event was lost or its run ended with a transient
|
||||
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.**
|
||||
A failed pre-release gate (golden, full smokes, performance, HA harness) does not
|
||||
send the issue back to review: fix, re-run what failed, record the exact command
|
||||
and result in the issue (`PROCESS.md` §11.4 — and its limits).
|
||||
|
||||
## Environments
|
||||
|
||||
**Local Windows checkout** is the day-to-day environment: repository-pinned Node
|
||||
and Python as in CI (`npm run toolchain:check` compares the machine with the pins
|
||||
CI actually uses — `.nvmrc` and `.python-version` are derived from the same
|
||||
sources, #496),
|
||||
`gh` authenticated. On the owner's machine do not trust the ambient PATH:
|
||||
`.\scripts\windows-toolchain.ps1 setup|check` owns a verified portable Node and
|
||||
dedicated `.venv-ci`, and its `npm`/`node`/`python`/`playwright` actions are the
|
||||
explicit pinned entrypoints (#557). It changes no persistent PATH and never
|
||||
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).
|
||||
The owner's Windows machine: `.\scripts\windows-toolchain.ps1 setup|check` owns
|
||||
the pinned Node and Python; WSL works from an ext4 clone with
|
||||
`bash scripts/wsl-setup.sh --verify` (`docs/DEVELOPMENT.md` › Local Windows
|
||||
workstation). `npm run toolchain:check` compares any machine with the pins CI
|
||||
uses. The full Home Assistant harness cannot run on native Windows; without an
|
||||
importable `homeassistant` pytest does not collect `test_ha_*.py` at all, so a
|
||||
green pure run proves nothing about the harness (`docs/TESTING.md`). Cloud
|
||||
agents have the harness at `.venv-backend/bin/python`.
|
||||
|
||||
+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
|
||||
source fingerprint. Scenario version, source fingerprint and every image hash
|
||||
are recorded in the [screenshot index](docs/images/screenshots.json), and
|
||||
`node scripts/check-docs.mjs` reports a stale fingerprint before a beta
|
||||
candidate. The full rule is in `PROCESS.md` (documentation screenshots).
|
||||
`node scripts/check-docs.mjs` reports a stale fingerprint: a warning on an
|
||||
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
|
||||
|
||||
@@ -100,16 +101,13 @@ npm install # also installs .githooks through the prepare script
|
||||
|
||||
### Why `--filter=blob:none` (#345)
|
||||
|
||||
A full clone is **215 MB of `.git`**; a blobless one is **26 MB** — measured, not
|
||||
estimated. Both carry all 1611 commits and all 182 tags, so ranges, `merge-base`
|
||||
and `git diff` across history work identically; `git diff origin/dev~3..origin/dev`
|
||||
in a blobless clone takes about a second and grows `.git` by one megabyte.
|
||||
|
||||
The difference is that historical *file contents* are fetched only if something
|
||||
actually asks for them. That matters here because 32% of the pack is documentation
|
||||
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.
|
||||
A blobless clone keeps every commit and tag, so ranges, `merge-base` and
|
||||
`git diff` across history work exactly as in a full clone; only historical *file
|
||||
contents* are fetched on demand. Most of the pack is exactly such content that
|
||||
almost nobody reads again — documentation screenshots re-captured with the UI and
|
||||
the committed bundle rewritten by every release candidate — so a blobless clone
|
||||
is several times smaller. To see the numbers for your own clone, compare
|
||||
`git count-objects -vH` in a blobless and in a full one.
|
||||
|
||||
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
|
||||
@@ -142,8 +140,5 @@ Linux CI or WSL (`bash scripts/wsl-setup.sh --verify`). Without an importable
|
||||
## Architecture
|
||||
|
||||
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
|
||||
the ones checked by `scripts/release-contract.mjs`, prereleases go through
|
||||
`npm run release:prerelease -- <tag> --issues=… --yes` (or the manual
|
||||
`Publish prerelease` workflow), and stable installable assets are published only
|
||||
by `release.yml` (#540).
|
||||
`docs/STATUS.md` (current state). Release mechanics live in one place:
|
||||
`docs/DEVELOPMENT.md` › Release.
|
||||
|
||||
+24
-98
@@ -215,9 +215,9 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
- **Скоуп не расширяется.** Найденное по пути становится новым issue в «Новое».
|
||||
Если находка блокирует — текущий issue уходит в «Заблокировано» со ссылкой.
|
||||
Попутных правок «раз уж я здесь» не бывает.
|
||||
- **Документация — в том же коммите,** что и поведение (действующая политика
|
||||
`docs/STATUS.md`): changelog RU+EN для пользовательского, `STATUS.md` для
|
||||
состояния, `DEVELOPMENT.md` для новых грабель, `ARCHITECTURE.md` для дизайна.
|
||||
- **Документация — в том же коммите,** что и поведение: changelog RU+EN для
|
||||
пользовательского, `STATUS.md` для состояния, `DEVELOPMENT.md` для новых
|
||||
грабель, `ARCHITECTURE.md` для дизайна.
|
||||
- **Выход:** локальный гейт зелёный (§8), хендофф-комментарий (§7.2).
|
||||
|
||||
### 2.7 Код-ревью
|
||||
@@ -273,7 +273,9 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
видит разницу.
|
||||
- **High блокируют.** Medium **в скоупе задачи** чинится в текущем issue:
|
||||
без High это жёлтый вердикт и возврат автору, фикс проходит повторный цикл.
|
||||
Medium **вне скоупа** — отдельный issue (#202).
|
||||
Medium **вне скоупа** — отдельный issue (#202). Жёлтый вердикт законен и
|
||||
тогда, когда все AC выполнены, если изменение не решает заявленный сценарий
|
||||
или ухудшает соседний.
|
||||
- **Вердикт привязан к SHA (#312).** Все числа и факты отчёта сверяются с
|
||||
`git rev-parse HEAD` непосредственно перед подведением итогов, а не с SHA,
|
||||
зафиксированным в начале разбора: во время ревью в ветку может прилететь
|
||||
@@ -286,8 +288,8 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
вызовом в `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.`, приватные
|
||||
члены порта и харнесса, байты `dist/`), база — `scripts/monolith-baseline.json`;
|
||||
@@ -358,10 +360,10 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
|
||||
Находкой остаётся другое: **SHA, мёртвый уже в момент публикации отчёта** —
|
||||
он означает, что значение сняли до `amend` или `rebase` и не сверили перед
|
||||
выводом, как требует §7.2. Это отличие не теоретическое: на #403 оба
|
||||
источника, автор и ревьюер, независимо назвали один и тот же осиротевший
|
||||
SHA, и следующий раунд восстанавливал коммит по содержимому диффа руками
|
||||
(issue #413). Конвейер теперь такую публикацию останавливает сам;
|
||||
выводом, как требует §2.7 («Вердикт привязан к SHA»). Это отличие не
|
||||
теоретическое: на #403 оба источника, автор и ревьюер, независимо назвали один
|
||||
и тот же осиротевший SHA, и следующий раунд восстанавливал коммит по
|
||||
содержимому диффа руками (issue #413). Конвейер теперь такую публикацию останавливает сам;
|
||||
3. по каждой находке предыдущего раунда показать, **чем именно она закрыта** —
|
||||
строкой кода или текста, а не заявлением автора;
|
||||
4. заново проверять только те AC, чьё доказательство дельта задевает;
|
||||
@@ -404,7 +406,7 @@ dev, ни при публикации документа код-ревью: ин
|
||||
каждом раунде: код изменился, а стоят они минуты. Тяжёлые — по дельте (§10.2).
|
||||
|
||||
**Разбор остаётся полным**, если дельта не локальна: ребейз на ушедший вперёд
|
||||
`dev` (после ребейза это другой код, §7.2), смена контракта поведения, задета
|
||||
`dev` (после ребейза это другой код, §10.4), смена контракта поведения, задета
|
||||
новая подсистема, либо объём дельты сопоставим с исходной задачей.
|
||||
|
||||
Сокращается объём **разбора, а не строгость**: правка по замечанию способна
|
||||
@@ -699,16 +701,6 @@ issue #NN
|
||||
прогон показал, почему это неверно: жёлтый там означал, что 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. Гейты
|
||||
@@ -734,10 +726,11 @@ npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
|
||||
# если менялось одно из зеркал junction limits
|
||||
```
|
||||
|
||||
**Новый код не добавляет `any`** (#342). В `src/**` уже 1034 вхождения явного
|
||||
`any` в 49 файлах; перетипизировать это одним заходом — месяц риска ради нуля
|
||||
пользовательской ценности, поэтому долг снимается при плановом извлечении
|
||||
подсистем (#425, прежний #34), а не разовой заменой. Гейт `scripts/no-new-any.mjs` судит
|
||||
**Новый код не добавляет `any`** (#342). Явного `any` в `src/**` — сотни
|
||||
вхождений (`node scripts/no-new-any.mjs --total`); перетипизировать это одним
|
||||
заходом — месяц риска ради нуля пользовательской ценности, поэтому долг
|
||||
снимается при плановом извлечении подсистем (#425, прежний #34), а не разовой
|
||||
заменой. Гейт `scripts/no-new-any.mjs` судит
|
||||
**только добавленные строки**: существующий долг на нетронутой строке законен,
|
||||
правка строки со старым `any` — новая ответственность. Исключение объявляется на
|
||||
той же строке, `// any-ok: <конкретная причина>`; голый маркер и причины вида
|
||||
@@ -776,8 +769,8 @@ npx tsc -p tsconfig.junction-parity.json && node scripts/fix-test-build.mjs \
|
||||
фронтенда делает его устаревшим. Выборка «по diff и AC» здесь не работает — diff
|
||||
всегда попадает, и решать нечего. Цена пропуска измерена: скриншоты не
|
||||
пересняли в #230 и #234, и `dev` стоял с красным job `docs`, пока это не нашли
|
||||
при следующей задаче (#237). Пересъёмка — `npm run build && node
|
||||
demo/docs/capture.mjs`, коммит вместе с задачей.
|
||||
при следующей задаче (#237). Пересъёмка — по двум абзацам выше, коммит
|
||||
вместе с задачей.
|
||||
|
||||
**Перф-смок в Validate зависит от диффа** (#473). Два glow-профиля
|
||||
гоняются всегда; при правке `src/iso-*` добавляется `large-house-isometric-v1`,
|
||||
@@ -1143,7 +1136,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
||||
- ветка уже содержит весь `dev` — ничего;
|
||||
- отстала и ребейзится чисто — ребейз, `push --force-with-lease`, ревью по
|
||||
приведённому состоянию. Факт ребейза передаётся в промпт, чтобы сработало
|
||||
правило §7.2 о полном разборе вместо дельты;
|
||||
правило §2.10 о полном разборе вместо дельты;
|
||||
- конфликт — возврат в `S6-in-progress` **до** запуска ревью. Цикл при этом не
|
||||
расходуется: код никто не читал, вердикта нет.
|
||||
|
||||
@@ -1165,7 +1158,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
||||
- если `dev` не двигался — push с `--force-with-lease` на текущую вершину;
|
||||
- если двигался — ребейз (конфликт — `S6-in-progress`, как раньше), сравнение
|
||||
patch-id проверенного и получившегося диффа (различие — `S7-code-review`: вердикт
|
||||
к другому диффу не применим, §7.2), публикация кандидата в ветку задачи, запуск
|
||||
к другому диффу не применим, §2.10), публикация кандидата в ветку задачи, запуск
|
||||
Validate с мутантами на ней (#510) и ожидание зелёного dispatch-прогона **на этом
|
||||
SHA** — push-прогон мутантов не несёт — и только затем push в `dev` с lease на ту
|
||||
вершину, поверх которой кандидат собран. Отклонённый lease — `dev` двинулся снова
|
||||
@@ -1215,7 +1208,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
||||
повторно **без вызова модели**, если последний документ этапа несёт записанный
|
||||
конвейером вердикт `green` с High 0 и дерево материала не изменилось ни в одном
|
||||
файле вне `docs/reviews/**` (сравнивает `git diff` по содержимому). Ребейз, правка
|
||||
теста, фикстуры или ТЗ дают отличие дерева и полный разбор — правило §7.2 не
|
||||
теста, фикстуры или ТЗ дают отличие дерева и полный разбор — правило §2.10 не
|
||||
ослабляется, оно просто не касается дерева, которое уже читали.
|
||||
|
||||
Цикл считается **по этапу**: вердикт по ТЗ не расходует бюджет код-ревью. Раньше
|
||||
@@ -1236,7 +1229,7 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
||||
- issue создан в **той же сессии до коммита**, метка `hotfix`;
|
||||
- ТЗ «как сделано» + раздел «почему нельзя было ждать»;
|
||||
- в течение 24 часов задача ретроспективно проходит код-ревью;
|
||||
- аварийность названа явно в релизном хендоффе (действующее правило `AGENTS.md`).
|
||||
- аварийность названа явно в релизном хендоффе.
|
||||
|
||||
### 11.3 Гигиена репозитория
|
||||
|
||||
@@ -1378,70 +1371,3 @@ Golden, браузерные смоки, performance и полный HA-харн
|
||||
|
||||
**Нарушение процесса — тоже 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
|
||||
```
|
||||
|
||||
### ⚠️ 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)
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
embeds a SHA-256 fingerprint of `src/` plus the locked package and
|
||||
Rollup/TypeScript build inputs; benchmark/golden runners fail before
|
||||
capturing anything when `demo/srv/assets/houseplan-card.js` is stale. Golden
|
||||
commands and the explicit review workflow are documented in
|
||||
`demo/golden/README.md`.
|
||||
capturing anything when `demo/srv/assets/houseplan-card.js` is stale;
|
||||
`demo/bundle-freshness.mjs` also verifies every manifest-listed asset hash, so a
|
||||
partially copied tree fails too. Golden commands and the explicit review
|
||||
workflow are documented in `demo/golden/README.md`.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cd /tmp/hpc && npm ci # once
|
||||
npm ci # once
|
||||
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: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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
bytes, so Windows CRLF conversion cannot disagree with the LF-tagged archive.
|
||||
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
|
||||
(`git archive <sha>:custom_components/houseplan`, deterministic), writes
|
||||
`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
|
||||
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
|
||||
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
|
||||
have their own staged path above. Bump the version
|
||||
everywhere in sync: `src/houseplan-card.ts` (CARD_VERSION), `package.json`,
|
||||
`custom_components/houseplan/manifest.json`, `custom_components/houseplan/const.py`.
|
||||
have their own staged path above. Bump the version in every source
|
||||
`scripts/release-contract.mjs` reads (`parseVersionSources`: `package.json`,
|
||||
`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
|
||||
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
|
||||
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;
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
@@ -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
|
||||
snapshots and changelog/release metadata; feature source changes belong in the
|
||||
preceding pre-release commit. Skip this step only for an explicit owner-approved
|
||||
emergency hotfix, and document the exception in the handoff.
|
||||
|
||||
## Reproducible scripts (data)
|
||||
|
||||
- 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.
|
||||
emergency hotfix, and document the exception in the 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.
|
||||
|
||||
## 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
|
||||
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).
|
||||
> This file is the **first thing to read** when resuming work. It captures the current
|
||||
> state, where everything lives, and how to continue safely.
|
||||
>
|
||||
> **Documentation policy (mandatory):** every change is documented *in the same
|
||||
> 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.
|
||||
> The current state for a resuming session: a generated snapshot, the current
|
||||
> cycle and the standing decisions that explain it. Rules are not here — the
|
||||
> 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
|
||||
> in the same commit as the change it describes (`PROCESS.md` §2.6).
|
||||
|
||||
## 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`) |
|
||||
<!-- status-snapshot:end -->
|
||||
|
||||
## Standing state and decisions
|
||||
|
||||
Prose kept by hand: decisions and the state they explain. Update a row in the
|
||||
same commit as the change it describes.
|
||||
## Current cycle and standing decisions
|
||||
|
||||
| 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. |
|
||||
| 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`. |
|
||||
| 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 |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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 | **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 |
|
||||
| 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 |
|
||||
| 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. |
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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. |
|
||||
| 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`. |
|
||||
| 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`. |
|
||||
| Localization | UI in en/ru/de/fr (`src/i18n/*.json`); German and French load lazily through the registry introduced by #62. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| Vacuums | Live puck, server-side trails and fit calibration are shipped; Roomba is not covered. Canonical: `docs/VACUUM.md`. |
|
||||
| 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`. |
|
||||
| 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. |
|
||||
|
||||
The feature surface since the 2026-07-17 snapshot and the early release
|
||||
milestones are archived in [`legacy/docs/STATUS-FEATURES.md`](../legacy/docs/STATUS-FEATURES.md)
|
||||
(#634, archived by #678): the feature surface is described by the changelog and
|
||||
the user guide, not by a parallel list.
|
||||
The feature surface and early milestones are described by the changelog and the
|
||||
user guide; the former parallel list is archived in
|
||||
[`legacy/docs/STATUS-FEATURES.md`](../legacy/docs/STATUS-FEATURES.md).
|
||||
|
||||
## Where things live
|
||||
## Where the rest lives
|
||||
|
||||
- **Source of truth:** the git repo on GitHub — work lands on `dev`, stable releases on
|
||||
`main`. In a sandbox session clone it from GitHub.
|
||||
- **Owner's folder:** `houseplan-card-src/houseplan-card` is the author's tree and
|
||||
`houseplan-card-src/hp-dev` the owner's worktree on `dev` (`AGENTS.md` › Working
|
||||
trees). The former file mirror `houseplan/houseplan-card/` is no longer maintained.
|
||||
- **Production config:** server-side on the HA instance, `.storage/houseplan.config` +
|
||||
`.storage/houseplan.layout` (backups `.bak-v1100` exist on the box).
|
||||
- Scope and personas — `docs/SCOPE.md`; process, statuses and gates —
|
||||
`PROCESS.md` (role digests in `docs/process/`).
|
||||
- Release mechanics, CI proof semantics and publication — `docs/DEVELOPMENT.md` › Release.
|
||||
- Local toolchain (Windows, WSL, the five-minute contour) — `docs/DEVELOPMENT.md`.
|
||||
- Tests — `docs/TESTING.md`: Node unit, pure backend, HA-harness backend (CI or
|
||||
WSL only; it uses repository-pinned Python plus
|
||||
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)
|
||||
contain task scope and acceptance criteria; their **labels** carry priority
|
||||
and workflow status (`PROCESS.md` §9). GitHub Projects is no longer used;
|
||||
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`.
|
||||
1. Read by role, as `AGENTS.md` › Read this first lists it.
|
||||
2. Clone `https://github.com/Matysh/houseplan-card` and run `npm ci` — it installs
|
||||
the hooks (`git config core.hooksPath` → `.githooks`).
|
||||
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`
|
||||
(`AGENTS.md` › Gates).
|
||||
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.
|
||||
4. The task's local gate is `npm run gate:small`; nothing is copied to a Home
|
||||
Assistant instance by hand (`PROCESS.md` §12).
|
||||
|
||||
+66
-30
@@ -163,37 +163,17 @@ mutant-jobs в доказательстве ревью (#541) не меняют
|
||||
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 --diff patch.diff # или `-` для stdin
|
||||
node scripts/no-new-any.mjs --total # весь долг src/**
|
||||
```
|
||||
|
||||
В `src/**` сейчас **1034 вхождения** явного `any` в 49 файлах — больше, чем
|
||||
называл аудит (330), потому что монолит с тех пор разделился и его обвязка
|
||||
уехала в `houseplan-editor-runtime.ts`. Разовая замена такого объёма — месяц
|
||||
риска ради нуля пользовательской ценности, поэтому долг снимается при плановом
|
||||
извлечении подсистем (#425, прежний #34). Гейт держит приращение на нуле.
|
||||
|
||||
Что он судит: **только добавленные строки** диапазона. Существующий `any` на
|
||||
нетронутой строке законен. Правка строки со старым `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 проверке нужен диапазон, а содержимое старых
|
||||
ревизий — нет.
|
||||
Правило — `PROCESS.md` §8 «Новый код не добавляет `any`»: судятся только
|
||||
добавленные строки, исключение — `// any-ok: <конкретная причина>` на той же
|
||||
строке, текст разбирает парсер TypeScript. Здесь — только механика: причина
|
||||
короче 12 символов или из списка заглушек скрипта (`todo`, `hack`, `потом`…) не
|
||||
проходит; код, дословно перенесённый блоком в другой файл того же диапазона,
|
||||
новым не считается (#592). В CI гейт вызывается в job `frontend`; её checkout
|
||||
получил полную историю без блобов, потому что diff-aware проверке нужен
|
||||
диапазон, а содержимое старых ревизий — нет.
|
||||
|
||||
## Тестовый фасад и приватное состояние (#629)
|
||||
|
||||
@@ -288,7 +268,7 @@ node scripts/pre-push-gate.mjs --max-smokes=3 --max-mutants=1
|
||||
Отставание от `dev` — предупреждение, а не провал набора: гейтом остаётся
|
||||
конвейер, который приводит ветку сам (#257) и забыть не может. Смысл локальной
|
||||
проверки в другом: после любого ребейза разбор на ревью становится полным, а не
|
||||
по дельте (§7.2), а конфликт всё равно чинится на машине автора — дешевле
|
||||
по дельте (PROCESS §2.10), а конфликт всё равно чинится на машине автора — дешевле
|
||||
узнать об этом до пуша, чем из комментария через сорок минут (#364). Отключается
|
||||
флагом `--no-rebase-check`. Замер на реальном
|
||||
диапазоне (`953f675~1..953f675`, правка `src/houseplan-card.ts`): типы 5 с,
|
||||
@@ -347,6 +327,31 @@ tsc, юниты, смоки и мутанты по диффу. Решение х
|
||||
Обойти, как и процессный гейт, можно через `git push --no-verify` — и тогда то же
|
||||
самое найдёт 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)
|
||||
|
||||
Один модуль, `scripts/check-inputs.mjs`, объявляет каждую проверку Validate
|
||||
@@ -461,6 +466,37 @@ golden отображаемая версия идёт через seam `displayVe
|
||||
создаёт непустой `panel-wide-view-light-en.png` и печатает длительность.
|
||||
Это ранняя обратная связь; независимый 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)
|
||||
|
||||
Репозиторий `Matysh/houseplan-e2e`: настоящий HA в docker, House Plan из
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
целевые смоки, `no-new-any`; по диффу — `golden:verify`, `check-docs`,
|
||||
`model-invariants`, `pytest tests_backend`, junction parity. Команды —
|
||||
в каноне ([§8](../../PROCESS.md#8-гейты)); `npm run gate:small` собирает
|
||||
обязательную часть (`AGENTS.md`, «Gates»).
|
||||
обязательную часть (`docs/TESTING.md`, «Локальный набор перед пушем»).
|
||||
- Бандл в коммит задачи не идёт: сборка переписывает отслеживаемый `dist/`,
|
||||
перед коммитом — `npm run bundle:clean`; хук `commit-msg` отклоняет пути
|
||||
бандла без трейлера `Release:` (#657, [§1](../../PROCESS.md#1-основное-правило)).
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
- Условие честности сужения: ревьюер обязан перечислить, какие гейты прогнал,
|
||||
какие нет и почему ([§8](../../PROCESS.md#8-гейты)).
|
||||
- Зелёный `pytest tests_backend` без Home Assistant скипает `test_ha_*.py` и
|
||||
ничего не доказывает — это «чего не проверял» (`AGENTS.md`, «Gates»;
|
||||
ничего не доказывает — это «чего не проверял» (`docs/TESTING.md`;
|
||||
[§8](../../PROCESS.md#8-гейты)).
|
||||
|
||||
## Повторный раунд
|
||||
|
||||
@@ -21,7 +21,7 @@ export function rebaseAdvice({ behind, base = 'origin/dev' }) {
|
||||
const commits = count === 1 ? 'коммит' : count < 5 ? 'коммита' : 'коммитов';
|
||||
return `ветка отстала от ${base} на ${count} ${commits}.`
|
||||
+ ` Конвейер приведёт её сам перед ревью, но после ребейза разбор станет`
|
||||
+ ` полным, а не по дельте (§7.2). Дешевле сделать это здесь:`
|
||||
+ ` полным, а не по дельте (§2.10). Дешевле сделать это здесь:`
|
||||
+ ` git fetch origin && git rebase ${base}`;
|
||||
}
|
||||
|
||||
@@ -46,5 +46,5 @@ export function devMovedNote({ moved, sha }) {
|
||||
const at = sha ? ` Материал ревью — \`${sha}\`.` : '';
|
||||
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`
|
||||
+ `Повторный прогон ревью — не формальность: после ребейза на новый \`dev\` это другой код, и принимать его без проверки нельзя. Цикл считается по этапу, лимит на код-ревью тратится отдельно от ревью ТЗ.`;
|
||||
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\` — новый заход ревью читает актуальный код.`;
|
||||
case 'validation-red':
|
||||
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 --base origin/dev --head HEAD
|
||||
* node scripts/no-new-any.mjs --diff patch.diff # или `-` для stdin
|
||||
* node scripts/no-new-any.mjs --total # весь долг src/** (#680)
|
||||
*
|
||||
* Зачем гейт, а не разовая типизация. В `src/**` сейчас 1034 вхождения явного
|
||||
* `any` в 49 файлах — перетипизировать это одним заходом значит месяц риска ради
|
||||
* нуля пользовательской ценности. Долг снимается при плановом извлечении
|
||||
* подсистем (#425, прежний #34). Задача гейта одна: не давать долгу расти.
|
||||
* Зачем гейт, а не разовая типизация. Явного `any` в `src/**` — сотни
|
||||
* вхождений (точное число печатает `--total`); перетипизировать это одним
|
||||
* заходом значит месяц риска ради нуля пользовательской ценности. Долг
|
||||
* снимается при плановом извлечении подсистем (#425, прежний #34). Задача
|
||||
* гейта одна: не давать долгу расти.
|
||||
*
|
||||
* Практический вред уже случался: несоответствие форм (`d.source.kind` против
|
||||
* строкового `source`) компилятор не поймал, потому что путь был через `any`, и
|
||||
@@ -51,8 +53,8 @@
|
||||
* добавление: повторная вставка того же блока остаётся новым кодом.
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
||||
import { dirname, join, relative, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript';
|
||||
|
||||
@@ -103,6 +105,34 @@ export function anyKeywordLines(path, text) {
|
||||
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` на строке, которую диапазон объявил добавленной.
|
||||
*
|
||||
@@ -303,6 +333,11 @@ function main(argv) {
|
||||
return index >= 0 && argv[index + 1] && !argv[index + 1].startsWith('--')
|
||||
? 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');
|
||||
let diff;
|
||||
if (diffArg) {
|
||||
@@ -352,7 +387,7 @@ function main(argv) {
|
||||
console.error('\nЛибо типизируйте, либо обоснуйте на той же строке:');
|
||||
console.error(' // any-ok: <конкретная причина, почему тип недоступен>');
|
||||
console.error('Существующий долг снимается при извлечении подсистем (#34, #342),');
|
||||
console.error('а не разовой заменой: в src/** его 1034 вхождения в 49 файлах.');
|
||||
console.error('а не разовой заменой; сколько его — node scripts/no-new-any.mjs --total.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ function manualGate(argv) {
|
||||
|
||||
// ---- приведена ли ветка к dev ---------------------------------------------
|
||||
// Конвейер ребейзит сам (#257), но после ребейза разбор становится полным, а не
|
||||
// по дельте (§7.2), и конфликт всплывает в комментарии через сорок минут вместо
|
||||
// по дельте (§2.10), и конфликт всплывает в комментарии через сорок минут вместо
|
||||
// машины автора. Поэтому предупреждение, а не гейт: гейтом остаётся конвейер.
|
||||
if (!flag('no-rebase-check')) {
|
||||
const fetched = capture('git', ['fetch', '-q', 'origin', base.replace(/^origin\//, '')]);
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
// Решения, принятые при реализации. Продуктового поведения не касаются, поэтому
|
||||
// приняты здесь, а не у владельца; ревьюер вправе оспорить любое.
|
||||
//
|
||||
// 1. «Release vX.Y.Z-beta.N candidate» — НЕ релизный коммит. Promotion-only по
|
||||
// AGENTS.md — это стабильный релиз; кандидат беты несёт саму работу и живёт
|
||||
// 1. «Release vX.Y.Z-beta.N candidate» — НЕ релизный коммит. Promotion-only
|
||||
// (`PROCESS.md` §3 п.16, `docs/DEVELOPMENT.md` › Release) — это стабильный релиз; кандидат беты несёт саму работу и живёт
|
||||
// по общим правилам, включая трейлер Issue.
|
||||
// 2. Классы дополнены: package.json, package-lock.json, pytest.ini, .gitignore,
|
||||
// .gitattributes, .githooks/** — класс B (конфигурация сборки и гейтов).
|
||||
|
||||
@@ -129,7 +129,7 @@ export function materialAnchorsFrom(text) {
|
||||
* issue. Коммита с таким именем в репозитории нет и не было: клон не мелкий,
|
||||
* `git rev-list --all` его не знает. Скорее всего значение снято до `amend`
|
||||
* или `rebase` при публикации — то есть проверка `git rev-parse HEAD` перед
|
||||
* выводом отчёта, которую требует §7.2, не выполнялась ни у автора, ни у
|
||||
* выводом отчёта, которую требует §2.7, не выполнялась ни у автора, ни у
|
||||
* ревьюера.
|
||||
*
|
||||
* Цена уже заплачена на следующем раунде: пункт «найти SHA, на котором получен
|
||||
@@ -182,7 +182,7 @@ export function danglingMaterialRefusal(
|
||||
+ 'Команда `git diff <sha>..HEAD` из PROCESS.md §2.10 на таком отчёте не'
|
||||
+ ' работает, а следующий раунд восстанавливает коммит по содержимому'
|
||||
+ ' диффа руками (#413). Сверьте SHA командой `git rev-parse HEAD`'
|
||||
+ ' непосредственно перед выводом отчёта — §7.2 требует именно этого,'
|
||||
+ ' непосредственно перед выводом отчёта — §2.7 требует именно этого,'
|
||||
+ ' а не значения, записанного до amend или rebase.';
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ export function anchorVerdictFrom(text) {
|
||||
* - текущее дерево отличается от якоря НИЧЕМ, кроме docs/reviews/** —
|
||||
* сравнение делает git по содержимому, так что ребейз на ушедший dev,
|
||||
* правка теста, фикстуры, скрипта или ТЗ в docs/specs дают отличие и
|
||||
* полный разбор (§7.2). Смена базы без изменения дерева невозможна:
|
||||
* полный разбор (§2.10). Смена базы без изменения дерева невозможна:
|
||||
* дерево ветки после ребейза включает содержимое dev.
|
||||
* Любое сомнение — `null`, и ревью идёт как обычно.
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@ import { conflictingPaths, devMovedNote, rebaseAdvice } from '../scripts/branch-
|
||||
// #364. Конвейер приводит ветку к dev сам (#257) и при конфликте возвращает
|
||||
// задачу, не тратя цикл ревью. Но конфликт всплывает в комментарии через сорок
|
||||
// минут, а чинится на машине автора; и после любого ребейза разбор становится
|
||||
// полным, а не по дельте (§7.2). Эти helpers переносят обнаружение туда, где
|
||||
// полным, а не по дельте (§2.10). Эти helpers переносят обнаружение туда, где
|
||||
// есть руки, и делают возврат адресным.
|
||||
|
||||
test('приведённая ветка совета не требует (#364)', () => {
|
||||
@@ -46,5 +46,5 @@ test('уход dev во время ревью описывается тольк
|
||||
assert.match(note, /продвинулся на 2 коммита/);
|
||||
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 {
|
||||
MOVED_BLOCK_MIN,
|
||||
addedLinesByFile, anyKeywordLines, blameLine, findNewAnyViolations, formatViolation,
|
||||
movedLinesByFile, parseAnyOk,
|
||||
movedLinesByFile, parseAnyOk, totalAny,
|
||||
} from '../scripts/no-new-any.mjs';
|
||||
|
||||
// #342. Цель гейта — не перетипизировать монолит, а не давать долгу расти. В
|
||||
// src/** сейчас 1034 вхождения явного any в 49 файлах; разовая замена — месяц
|
||||
// риска ради нуля пользовательской ценности, поэтому долг снимается при
|
||||
// извлечении подсистем (#34), а гейт держит приращение на нуле.
|
||||
// src/** сотни вхождений явного any (`node scripts/no-new-any.mjs --total`);
|
||||
// разовая замена — месяц риска ради нуля пользовательской ценности, поэтому
|
||||
// долг снимается при извлечении подсистем (#34), а гейт держит приращение на нуле.
|
||||
|
||||
const file = (text, addedLines) => ({
|
||||
path: 'src/probe.ts', text, addedLines: new Set(addedLines),
|
||||
@@ -241,3 +241,12 @@ test('#592 перенос с изменённым отступом перено
|
||||
].join('\n');
|
||||
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