chore: simplify root tooling and repository boundaries #10

Merged
ksv741 merged 1 commits from codex/standalone-repositories into develop 2026-09-15 21:30:48 +03:00
15 changed files with 89 additions and 358 deletions
+7 -10
View File
@@ -9,9 +9,8 @@
3. Расширь подходящий owner/helper/pattern; выбирай минимальное решение, 3. Расширь подходящий owner/helper/pattern; выбирай минимальное решение,
полностью закрывающее текущую задачу. Новый файл сам по себе не улучшение. полностью закрывающее текущую задачу. Новый файл сам по себе не улучшение.
Уменьшай количество способов решать одну техническую задачу. Во frontend Уменьшай количество способов решать одну техническую задачу: конфигурацию
сравни query keys, formatters, class composition; в backend — pagination, release-it и commitlint поддерживай средствами этих инструментов. Сходный синтаксис
error mapping, repositories и обращения к одному provider. Сходный синтаксис
без общей ответственности не требует DRY. Критерий dependency — в без общей ответственности не требует DRY. Критерий dependency — в
[dependencies](dependencies.md). [dependencies](dependencies.md).
@@ -23,10 +22,8 @@ Consistency с подходящим project pattern предпочтительн
Сначала используй native API/установленный stack; не строй на гипотетических Сначала используй native API/установленный stack; не строй на гипотетических
consumers и будущих requirements. consumers и будущих requirements.
Backend: CQRS, Event Sourcing, Domain Events, Unit of Work, Command Bus, Не вводи shared tooling для других репозиториев как обязательную зависимость
generic repositories, abstract factories, custom DI и elaborate domain layers их сборки, CI или маршрута инструкций. Каждый repo владеет своими checks.
не вводятся только ради «best practice». Frontend: wrapper над Mantine, global
store и дополнительная OpenAPI hash/schema система не создаются автоматически.
Trust-boundary validation, authorization, atomicity, accessibility и необходимая Trust-boundary validation, authorization, atomicity, accessibility и необходимая
regression proof остаются частью полного решения. regression proof остаются частью полного решения.
@@ -44,8 +41,8 @@ low-risk, behavior-preserving проблемы, которые легко про
Устойчивый pattern должен находиться через код и manifest. Добавляй convention, Устойчивый pattern должен находиться через код и manifest. Добавляй convention,
только если следующей сессии всё ещё нужно явное project decision rule. только если следующей сессии всё ещё нужно явное project decision rule.
У каждого правила один owner; AGENTS содержит trigger и ссылку. У каждого правила один owner; AGENTS содержит trigger и ссылку.
Mental model живёт в `.ai/architecture.md`, intent — в specs, системные знания — Устройство root tooling находится в README и конфигурации; их чтение не
в docs. Audit/backlog не помещай в conventions. требует соседних checkout. Audit/backlog не помещай в conventions.
Legacy фиксируется отдельно; новый diff не добавляет нарушений. При migration Legacy фиксируется отдельно; новый diff не добавляет нарушений. При migration
закрывай подтверждённые baseline findings с evidence, сохраняя область проверки. закрывай подтверждённые baseline findings с evidence, сохраняя область проверки.
@@ -65,4 +62,4 @@ Numeric policy называй по смыслу и единицам (`TIMEOUT_MS
Индексы, `index + 1`, `Math.max(value, 0)` и обычные labels/log messages Индексы, `index + 1`, `Math.max(value, 0)` и обычные labels/log messages
не выноси в constants без semantic benefit. Локальный lint/i18n profile всё равно не выноси в constants без semantic benefit. Локальный lint/i18n profile всё равно
применим. Не создавай tests существования констант или custom magic-string analyzer. применим. Не создавай tests существования констант или custom magic-string analyzer.
Различия TS/генератора принадлежат local conventions frontend/backend. Правила приложений принадлежат их локальным conventions.
+2 -6
View File
@@ -14,9 +14,6 @@ native API и зрелую небольшую dependency: parsing, serialization
debounce, class composition. Три `Array.map` не требуют utility library. debounce, class composition. Три `Array.map` не требуют utility library.
Не продолжай копировать boilerplate, если один принятый способ дешевле сопровождать. Не продолжай копировать boilerplate, если один принятый способ дешевле сопровождать.
`clsx` сейчас не принят как direct dependency. Разные способы соединить классы
сначала требуют подтверждённого повторения; устанавливать его заранее не нужно.
## При выборе пакета ## При выборе пакета
- Укажи конкретную потребность, альтернативу из текущего stack и существенную - Укажи конкретную потребность, альтернативу из текущего stack и существенную
@@ -26,8 +23,7 @@ debounce, class composition. Три `Array.map` не требуют utility libr
- Сохраняй exact versions и lockfile. Перед Node-командами активируй `.nvmrc` - Сохраняй exact versions и lockfile. Перед Node-командами активируй `.nvmrc`
через `nvm use --silent`; воспроизводимая установка — `npm ci` в нужном repo. через `nvm use --silent`; воспроизводимая установка — `npm ci` в нужном repo.
- Peer-конфликты решай совместимыми версиями, без `--force`/`--legacy-peer-deps`. - Peer-конфликты решай совместимыми версиями, без `--force`/`--legacy-peer-deps`.
Известная frontend граница TypeScript/openapi-typescript — в Версии и compatibility принадлежат местному manifest/lockfile.
[README](../../frontend/README.md#проверка); версии принадлежат manifest/lockfile.
- После изменения запускай relevant install/type/build/behavior checks и - После изменения запускай relevant install/type/build/behavior checks и
existing security workflow. Не добавляй второй analyzer, когда задача решается `npm audit` при изменении зависимостей. Не добавляй второй analyzer, когда задача решается
настройкой существующего; [карта проверок](guardrails.md). настройкой существующего; [карта проверок](guardrails.md).
+3 -5
View File
@@ -7,16 +7,14 @@
- Commit содержит одну связанную задачу и её необходимые проверки/документацию. - Commit содержит одну связанную задачу и её необходимые проверки/документацию.
Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками. Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками.
- Commits и заголовки PR — Conventional Commits; допустим `spec:`. - Commits и заголовки PR — Conventional Commits; допустим `spec:`.
Во frontend номер связанной issue добавляй в конец заголовка (`fix: … #12`); Формат проверяет local commitlint; правила других repo принадлежат их AGENTS.
без связанной issue номер не нужен. Формат проверяет local commitlint.
- Перед commit проверь `git diff --cached --name-status` и сам staged diff - Перед commit проверь `git diff --cached --name-status` и сам staged diff
в каждом affected repo. Root tracking ограничен [.gitignore](../../.gitignore): в каждом affected repo. Root tracking ограничен [.gitignore](../../.gitignore):
новый общий файл получает точечный allowlist и проверку новый общий файл получает точечный allowlist; проверь staged paths вручную. `git add -f` не используется.
[tracking](../../test-workspace-tracking.py). `git add -f` не используется.
- Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или - Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или
перенос опубликованных тегов как обычную очистку. Для опасной операции нужны перенос опубликованных тегов как обычную очистку. Для опасной операции нужны
явная необходимость, сохранение данных и соответствующая авторизация. явная необходимость, сохранение данных и соответствующая авторизация.
- Обычный PR допускает squash. Release/hotfix, синхронизация main/develop, - Обычный PR допускает squash. Release/hotfix, синхронизация main/develop,
release-конфигурация и CI refs требуют чтения release-конфигурация требуют чтения
[процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy, [процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy,
версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай. версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай.
+12 -45
View File
@@ -1,48 +1,15 @@
# Conventions → deterministic guardrails # Root проверки
Читай при выборе проверки или изменении tooling. Команды выполняются из repo Root CI содержит только `commits`: штатный commitlint проверяет заголовок PR
владельца после `nvm use --silent`. Config/manifest задаёт фактический scope; и коммиты между BASE и HEAD. Установка — `npm ci --ignore-scripts`, Node — из
таблица не заменяет semantic review и не утверждает, что все checks уже запущены. местной `.nvmrc`. Самописные проверки и дополнительные CI workflow не добавляются.
| Convention | Существующий check | Достаточность / следующий шаг | Локально перед Node-командами выполни `nvm use --silent`. Для изменений проверь
| --- | --- | --- | JSON/config syntax, тексты, ссылки и `git diff --check`; новые файлы просмотри
| Git multi-repo tracking | root `python3 test-workspace-tracking.py`, `.gitignore` | Проверяет exact allowlist и сохранность fictional data/nested Git; обновлять вместе с allowlist | отдельно. Перед commit проверь staged paths относительно allowlist `.gitignore`.
| Conventional Commits | local commitlint + commits workflow | Syntax покрыт; атомарность и scope — review | Зависимости устанавливай через `npm ci`; при их изменении выполни `npm audit`.
| Dependencies | exact manifest/lockfile, `npm ci`, security workflows (audit/Gitleaks) | Reuse, peer compatibility, ценность dependency — review; новый analyzer не нужен |
| Frontend types/style/literals | TypeScript strict, `erasableSyntaxOnly`; Biome `npm run lint` | Props profile, i18n и symbolic strings требуют review; literals exclusions — в Biome |
| Frontend FSD | dependency-cruiser `npm run deps` | Upward imports, cycles, public entry points покрыты; same-layer запрет неполный, semantic ownership не проверяется |
| Unused frontend code/deps | `npm run knip`, TS unused locals/params | Existing tool достаточно; не добавлять второй unused analyzer |
| API generation | frontend `openapi:check`; backend `test:openapi-contract` | Первый сравнивает generated types со snapshot; второй — Nest schema со snapshot. Ни один не доказывает runtime validation errors |
| Query/auth/error/finance behavior | Vitest + RTL/MSW; backend Jest/SQLite/HTTP | Test Intent выбирает риск и уровень, без автоматического теста для каждого файла |
| Storybook | `typecheck`, `deps:storybook`, `build:catalog` | Imports/types/build покрыты; `play`, visual/keyboard/browser behavior проверять отдельно |
| Backend types/style | ESLint/Prettier, app/test TypeScript | Zero-warning gate; `no-explicit-any` отключён, часть unsafe rules включена. Не обещать полного запрета `any` |
| Backend modules/domain | TypeScript и semantic review | Отдельного architecture analyzer нет. При повторных нарушениях сначала рассмотреть existing ESLint `no-restricted-imports`, без нового custom analyzer |
| DB ownership/atomicity | Prisma schema/constraints, `test:sqlite`, `test:e2e`, `test:durability` | Mock Prisma недостаточно. Relevant suites на disposable DB, без reset пользовательской базы |
| Logging/security | Logging/auth/access tests, configured HTTP tests, Gitleaks | Allowlist/redaction имеют границы. Встроенный ESLint `no-console` можно включить отдельно; semantic security review обязателен |
| Markdown/routing | docs markdownlint-cli2 + relative-links, `git diff --check` | Existing docs gate охватывает docs, а не соседние `.ai`; для них запустить тот же установленный CLI с явным списком путей и docs config |
## Ratchet без новой инфраструктуры Release-it использует встроенные проверки ветки, чистого дерева и наличия
коммитов. Перед подготовкой релиза вручную проверь теги и соответствие версии
- Existing error gates остаются обязательными; не ослабляй их ради legacy. последнему принятому тегу; повторно не повышай уже подготовленную версию.
- До расширения FSD rule получи candidate violations существующим depcruise. Процесс — в [README](../../README.md#релизы-и-ветки).
Его `--ignore-known` допускает native baseline; сначала проверь формат через
`--help`. Не игнорируй целые directories. Обновление baseline принимается
только с evidence закрытия/явным обоснованием нового исключения.
- Semantic findings учитываются stable IDs в
[inventory](../migrations/conventions-v1/audit.md) и закрываются vertical slices
по [plan](../migrations/conventions-v1/plan.md). Это reviewed inventory,
не автоматический полный счётчик нарушений.
- Новый analyzer/custom baseline CI не создаётся. Если существующей проверки
недостаточно, сначала сформулируй повторяющийся regression и стоимость
поддержки предлагаемого rule.
## Выбор финального gate
Markdown/routing-only: проверить все новые/изменённые Markdown, ссылки/якоря,
достижимость тем, смысл и `git diff --check`. Root allowlist change дополнительно
требует tracking-тест. Не запускать application builds/coverage только ради текста.
Code/dependency changes: full `npm run verify` affected repo после lockfile install;
targeted tests ускоряют итерацию, но не подменяют required gate. Wire changes
дополнительно требуют backend contract check, HTTP/DB changes — relevant isolated
suites по local Testing. Форматирующие `lint:fix`/`format` — mutations, не proof.
+2 -2
View File
@@ -22,5 +22,5 @@ jobs:
BASE_SHA: ${{ github.event.pull_request.base.sha }} BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: | run: |
printf '%s\n' "$PR_TITLE" | npm run commitlint -- --strict printf '%s\n' "$PR_TITLE" | npm exec --no -- commitlint --strict
npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA" npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
-31
View File
@@ -1,31 +0,0 @@
name: Workspace security
on:
push:
branches:
- '**'
pull_request:
schedule:
- cron: '17 5 * * *'
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
- name: Record checked revision
run: git rev-parse HEAD
- name: Scan history with Gitleaks 8.30.1
run: |
curl --fail --location --silent --show-error https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz -o /tmp/gitleaks.tar.gz
echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' | sha256sum --check
mkdir -p /tmp/gitleaks-bin
tar -xzf /tmp/gitleaks.tar.gz -C /tmp/gitleaks-bin gitleaks
/tmp/gitleaks-bin/gitleaks git --log-opts=--all --redact=100 --no-banner
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Audit runtime and development dependencies
run: npm audit --include=dev --audit-level=low
-17
View File
@@ -1,17 +0,0 @@
name: Workspace verify
on:
push:
branches: ['**']
pull_request:
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/actions/setup-node@v4
with:
node-version-file: .nvmrc
- run: npm ci
- run: npm run verify
-7
View File
@@ -3,7 +3,6 @@
!/.gitignore !/.gitignore
!/AGENTS.md !/AGENTS.md
!/README.md !/README.md
!/test-workspace-tracking.py
!/.ai/ !/.ai/
/.ai/* /.ai/*
!/.ai/conventions/ !/.ai/conventions/
@@ -26,14 +25,8 @@
!/CHANGELOG.md !/CHANGELOG.md
!/commitlint.config.cjs !/commitlint.config.cjs
!/renovate.json !/renovate.json
!/test-release.cjs
!/scripts/
/scripts/*
!/scripts/check-release.cjs
!/.gitea/ !/.gitea/
/.gitea/* /.gitea/*
!/.gitea/workflows/ !/.gitea/workflows/
/.gitea/workflows/* /.gitea/workflows/*
!/.gitea/workflows/commits.yml !/.gitea/workflows/commits.yml
!/.gitea/workflows/verify.yml
!/.gitea/workflows/security.yml
-3
View File
@@ -18,9 +18,6 @@
"gitlab": { "gitlab": {
"release": false "release": false
}, },
"hooks": {
"after:git:init": "node scripts/check-release.cjs"
},
"plugins": { "plugins": {
"@release-it/conventional-changelog": { "@release-it/conventional-changelog": {
"infile": "CHANGELOG.md", "infile": "CHANGELOG.md",
+23 -22
View File
@@ -1,31 +1,32 @@
# WealthLedger workspace # WealthLedger workspace
`frontend`, `backend`, `docs`, `specs` и root — независимые Git-репозитории. Root содержит workspace-навигацию и конфигурацию стандартных инструментов.
Самописные скрипты не добавляются; CI выполняет только commitlint.
`frontend`, `backend`, `docs`, `specs` — самостоятельные Git-репозитории.
Root tracking ограничен allowlist; локальные данные и nested repos в него Root tracking ограничен allowlist; локальные данные и nested repos в него
не добавляются. `references/**`, dated audits и архивы — read-only. не добавляются. `references/**`, dated audits и архивы — read-only.
Структура/запуск — в [README](README.md).
Начни с результата задачи и affected areas. Прочитай local AGENTS затронутого Инструкции root относятся только к root. Работа внутри другого репозитория
repo; проследи поведение/инварианты и найди owner с 1–3 примерами до реализации. начинается с его собственного AGENTS; он содержит все обязательные правила
Код показывает механизм, а случайный аналог не переопределяет контракт. без наследования root conventions и без чтения соседних checkout.
Работай напрямую; OpenSpec/SDD lifecycle нужен только по явному запросу. Публичные API artifacts передаются явно; CI каждого repo использует локальные
входы. Справочные источники не становятся обязательной цепочкой инструкций.
| Задача / граница | Прочитать | Начни с результата и affected areas. До edits проследи поведение/invariants,
найди owner и 1–3 примера. Работай напрямую; OpenSpec/SDD — по явному запросу.
| Root задача | Прочитать |
| --- | --- | | --- | --- |
| Frontend / backend | [Frontend](frontend/AGENTS.md) / [Backend](backend/AGENTS.md), затем только активированные темы | | Helper/pattern, refactor, scope | [Code health](.ai/conventions/code-health.md) |
| Docs / specs | [Docs AGENTS](docs/AGENTS.md) / [Specs AGENTS](specs/AGENTS.md) | | Dependency/tooling | [Dependencies](.ai/conventions/dependencies.md), [Guardrails](.ai/conventions/guardrails.md) |
| Новая функция, неясное ожидание, межрепозиторное поведение | [Docs entry](docs/README.md) → нужная область/spec/ADR; локальный build/lint bug начинает с repo instructions/code | | Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) |
| Новый helper/hook/wrapper/pattern, refactor, выбор scope | [Code health / change scope](.ai/conventions/code-health.md) | | Git/branch/commit/PR/tracking | [Git](.ai/conventions/git.md) |
| Dependency или tooling | [Dependencies](.ai/conventions/dependencies.md), [guardrails](.ai/conventions/guardrails.md) | | Release/hotfix | [README](README.md#релизы-и-ветки) |
| Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) и local Testing |
| Git, ветка, commit, PR, tracking | [Git](.ai/conventions/git.md) |
| Release/hotfix, main↔develop, release config/CI refs | [Release process](README.md#релизы-и-ветки) |
| Migration существующих conventions findings | [Audit/plan](.ai/migrations/conventions-v1/plan.md) |
Перед Node-командой активируй `.nvmrc` affected repo через `nvm use --silent`. Перед Node-командой активируй местную `.nvmrc`: `nvm use --silent`.
При новой границе и перед завершением повтори routing по staged/unstaged/new При новой границе и перед завершением повтори routing по staged/unstaged/new
files; сохраняй чужие изменения. Missing required source или конфликт требований files; сохраняй чужие изменения. Missing source/конфликт требований сообщи
сообщи до зависимого решения. После code diff выполни до зависимого решения. После diff выполни
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review); проверь результат [Boy Scout review](.ai/conventions/code-health.md#boy-scout-review) и
по рискам и [relevant gates](.ai/conventions/guardrails.md#выбор-финального-gate). [relevant gates](.ai/conventions/guardrails.md). Запиши краткие команды,
Сохрани краткие команды/результаты и ограничения; успех lint не заменяет semantic review. результаты и ограничения; lint не заменяет semantic review.
+40 -36
View File
@@ -2,7 +2,7 @@
Локальное приложение для портфеля T-Bank, операций, дохода и будущих выплат, Локальное приложение для портфеля T-Bank, операций, дохода и будущих выплат,
а также ручного учёта банковских карт и расходов. а также ручного учёта банковских карт и расходов.
[Обзор продукта](docs/overview.md) и [документация системы](docs/README.md). [Обзор продукта](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/overview.md) и [документация системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md).
## Структура workspace ## Структура workspace
@@ -12,10 +12,10 @@ Workspace состоит из пяти независимых Git-репозит
| Путь от root | Репозиторий Gitea | Назначение | | Путь от root | Репозиторий Gitea | Назначение |
| --- | --- | --- | | --- | --- | --- |
| `.` | `WealthLedger/main` | Вход в workspace, инструкции и правила root tracking | | `.` | `WealthLedger/main` | Вход в workspace, инструкции и правила root tracking |
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](frontend/README.md) | | `frontend/` | `WealthLedger/frontend` | [React/Vite UI](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md) |
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](backend/README.md) | | `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md) |
| `docs/` | `WealthLedger/docs` | [Объяснение системы](docs/README.md) | | `docs/` | `WealthLedger/docs` | [Объяснение системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md) |
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](specs/README.md) | | `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md) |
### Получение checkout ### Получение checkout
@@ -34,13 +34,14 @@ git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/specs.git specs
Для существующего workspace клонируйте только отсутствующие checkout в пустые Для существующего workspace клонируйте только отсутствующие checkout в пустые
каталоги. Проверяйте состояние каждого repo отдельно, например каталоги. Проверяйте состояние каждого repo отдельно, например
`git -C frontend status`. Обновление main не обновляет соседние repos; `git -C frontend status`. Обновление main не обновляет соседние repos;
общего lockfile revisions нет. Относительные ссылки требуют указанных имён каталогов. общего lockfile revisions нет. Такая раскладка удобна для совместной работы,
но каждый checkout можно использовать отдельно.
### Что хранится в main ### Что хранится в main
Root отслеживает входные документы, общие engineering conventions и их migration Root отслеживает входные документы, собственные engineering conventions и исторический migration
inventory в `.ai/`, npm/release-конфигурацию, changelog, inventory в `.ai/`, npm/release-конфигурацию, changelog,
проверки tracking/release и три CI workflow. Точный allowlist — в `.gitignore`. один CI workflow с commitlint. Точный allowlist — в `.gitignore`.
Остальные root-пути игнорируются по умолчанию: Остальные root-пути игнорируются по умолчанию:
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`, вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
`design/`, `todo/` и локальные настройки инструментов. `design/`, `todo/` и локальные настройки инструментов.
@@ -61,7 +62,7 @@ inventory в `.ai/`, npm/release-конфигурацию, changelog,
После смены major Node переустановите native dependencies через `npm ci`. После смены major Node переустановите native dependencies через `npm ci`.
Разрешения install scripts заданы в `package.json` соответствующего repo. Разрешения install scripts заданы в `package.json` соответствующего repo.
Для нового backend создайте `.env` по [backend README](backend/README.md), Для нового backend создайте `.env` по [backend README](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md),
замените оба placeholder-секрета и проверьте `DATABASE_URL`. замените оба placeholder-секрета и проверьте `DATABASE_URL`.
Существующий `.env` сохраняйте. Из backend: Существующий `.env` сохраняйте. Из backend:
@@ -74,13 +75,13 @@ npm run start:dev
Build генерирует Prisma client. Перед миграцией существующей базы сохраните Build генерирует Prisma client. Перед миграцией существующей базы сохраните
согласованный backup и ключ шифрования; процедуру и ограничения отката смотрите согласованный backup и ключ шифрования; процедуру и ограничения отката смотрите
в [описании хранения](docs/architecture/containers.md#сохранение-и-восстановление-данных). в [описании хранения](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/architecture/containers.md#сохранение-и-восстановление-данных).
Известная ошибка fresh migration при `RUST_LOG=warn/error` устранена в Известная ошибка fresh migration при `RUST_LOG=warn/error` устранена в
`backend/prisma.config.ts`: CLI удаляет унаследованную переменную. `backend/prisma.config.ts`: CLI удаляет унаследованную переменную.
Используйте штатный `prisma migrate deploy` из backend; прежний Prisma blocker Используйте штатный `prisma migrate deploy` из backend; прежний Prisma blocker
снят. [Подробности и границы проверки](backend/README.md#реальная-sqlite-и-сохранность-данных). снят. [Подробности и границы проверки](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#реальная-sqlite-и-сохранность-данных).
В другом терминале подготовьте `.env` по [frontend README](frontend/README.md), В другом терминале подготовьте `.env` по [frontend README](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md),
затем из frontend: затем из frontend:
```bash ```bash
@@ -99,7 +100,7 @@ npm run dev
OpenSpec не нужен для запуска приложения и применяется по явному запросу. OpenSpec не нужен для запуска приложения и применяется по явному запросу.
Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0. Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0.
Store `wealth-ledger-specs` расположен в `specs/openspec/`. Store `wealth-ledger-specs` расположен в `specs/openspec/`.
[Работа с OpenSpec](specs/AGENTS.md), [команды validation](specs/README.md#проверка). [Работа с OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/AGENTS.md), [команды validation](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md#проверка).
## Проверки ## Проверки
@@ -107,17 +108,18 @@ Store `wealth-ledger-specs` расположен в `specs/openspec/`.
| Где | Команда | Что проверяет | | Где | Команда | Что проверяет |
| --- | --- | --- | | --- | --- | --- |
| root | `python3 test-workspace-tracking.py` | Массовый add на вымышленных данных; сохранность файлов и вложенных Git | | root | `npm exec --no -- commitlint --strict --from <base> --to <head>` | Формат сообщений коммитов |
| root | `git diff --check` | Whitespace в изменениях root | | root | `git diff --check` | Whitespace в изменениях root |
| frontend | `npm run verify` | Типы, lint, тесты, зависимости, OpenAPI snapshot и сборки | | frontend | `npm run verify` | Типы, lint, тесты, зависимости, OpenAPI snapshot и сборки |
| backend | `npm run verify` | Lint, типы, unit tests и build | | backend | `npm run verify` | Lint, типы, unit tests и build |
| backend | `npm run test:e2e` | HTTP/SQLite suites и контракт с соседним frontend | | backend | `npm run test:e2e` | HTTP/SQLite suites и локальный OpenAPI artifact |
| docs | `npm run verify` | Markdown, относительные ссылки и diff | | docs | `npm run verify` | Markdown, относительные ссылки и diff |
Python 3 нужен только для проверки root tracking. Backend e2e использует Root CI выполняет только commitlint: проверяет сообщения коммитов и заголовок PR.
Allowlist root проверяется вручную по staged paths. Backend e2e использует
временные базы; verify отдельно не доказывает HTTP/DB-сценарии или доступность временные базы; verify отдельно не доказывает HTTP/DB-сценарии или доступность
T-Bank. Полные команды и ограничения принадлежат README T-Bank. Полные команды и ограничения принадлежат README
[frontend](frontend/README.md#проверка) и [backend](backend/README.md#проверка). [frontend](https://git.personal.ksv741.ru/WealthLedger/frontend/src/commit/9f7e342333b80bfacfd5bc394a2dcd0613d33ff3/README.md#проверка) и [backend](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md#проверка).
Для изменения проекта начните с [AGENTS](AGENTS.md). Для изменения проекта начните с [AGENTS](AGENTS.md).
## Релизы и ветки ## Релизы и ветки
@@ -144,18 +146,19 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`. 1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
Проверить, что текущий `develop` содержит последний релиз из `main`. Проверить, что текущий `develop` содержит последний релиз из `main`.
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`. Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
2. Для backend и docs обновить `.ci/refs.json`: полные SHA совместимых соседних 2. Проверить локальные входы и публичные artifacts выпускаемого репозитория.
репозиториев. Эти revisions должны быть доступны в Gitea. Проверить именно Совместимость развёрнутых участников согласовать отдельно через публичные
их совместимость; движущиеся ветки не являются фиксацией состава релиза. контракты; CI не скачивает соседние checkout и не требует их SHA.
Закоммитить обновлённые pins до подготовки версии.
3. Выполнить `npm run release:preview`. Проверить номер и changelog, затем 3. Выполнить `npm run release:preview`. Проверить номер и changelog, затем
`npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет `npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет
`package.json`, lockfile и `CHANGELOG.md`; теги и push не выполняет. `package.json`, lockfile и `CHANGELOG.md`; теги и push не выполняет.
Не использовать обход branch/clean/tag guards. Незакоммиченные файлы, В root встроенные проверки release-it контролируют ветку, чистое дерево и
неподходящая ветка, отсутствие изменений и уже подготовленная версия блокируют выпуск. наличие коммитов. Теги и соответствие версии последнему принятому тегу
проверяются вручную; уже подготовленную версию повторно не повышать.
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную 4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком ветку: `git push -u origin HEAD`. Открыть PR в `main` с заголовком
`chore(release): X.Y.Z`. После зелёных `verify`, `security`, `commits` `chore(release): X.Y.Z`. После зелёных обязательных проверок (`commits` в root;
`verify`, `security`, `commits` в остальных repo)
выполнить **fast-forward-only merge**. Если `main` изменился, сначала выполнить **fast-forward-only merge**. Если `main` изменился, сначала
согласовать историю и повторить проверку кандидата; не обходить защиту. согласовать историю и повторить проверку кандидата; не обходить защиту.
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный 5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
@@ -185,25 +188,26 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`. `release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
Если ломается публичный контракт, major сохраняется и для hotfix. Если ломается публичный контракт, major сохраняется и для hotfix.
Backend CI проверяет runtime OpenAPI с frontend; docs CI — межрепозиторные Каждый CI использует только свой checkout. Backend генерирует OpenAPI без сервера,
ссылки. Для `main`, PR в `main` и `release/*` соседи берутся из `.ci/refs.json`; сверяет генератор с configured app и не хранит JSON в Git; frontend проверяет types по входному
для разработки — из `develop`. SHA записываются в логи. Для изменения сразу snapshot. Docs проверяет внутренние ссылки; внешние источники даются как
нескольких репозиториев сначала согласовать их revisions, затем проверять справочные веб-ссылки. Обновление API artifact потребителя — отдельное действие.
релизный набор; порядок публикации сам по себе не доказывает совместимость. Зелёные локальные gates и порядок публикации не доказывают совместимость
развёрнутых версий. Процедуры выпуска находятся также в README каждого repo;
доступ к root для выпуска другого репозитория не требуется.
В Gitea для `main` и `develop` нужны запрет прямого push и force-push, В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
обязательные проверки `verify`, `security`, `commits` и запрет обхода правил обязательные проверки: в root только `commits`, в остальных repo —
`verify`, `security`, `commits`; также нужен запрет обхода правил
администратором. Требование отдельного одобрения review не вводится для администратором. Требование отдельного одобрения review не вводится для
разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже
отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу
релиза; их удаление или перенос не входит в обычный процесс. релиза; их удаление или перенос не входит в обычный процесс.
`node test-release.cjs` из root проверяет настоящий release-it во временном Git. В root нет собственного test/verify runner и release hooks. Release-it и
Для проверки конфигурации соседа после установки его dependencies: commitlint используются напрямую. Остальные репозитории сохраняют свои gates.
`node test-release.cjs frontend` (аналогично backend, docs, specs). Если в защите веток root ранее были обязательными `verify` и `security`, их
Тест не публикует refs и не использует рабочие базы. Root `npm run verify` нужно убрать из required checks в Gitea; YAML не меняет настройки сервера.
включает tracking и этот тест; frontend/backend/docs сохраняют свои gates,
backend дополнительно требует `npm run test:e2e`.
Npm publishing, автоматический deploy и карточки Gitea Releases не включены. Npm publishing, автоматический deploy и карточки Gitea Releases не включены.
Граница выпуска — принятый коммит, changelog и Git-тег. Граница выпуска — принятый коммит, changelog и Git-тег.
-1
View File
@@ -6,7 +6,6 @@
"node": "24.21.0" "node": "24.21.0"
}, },
"scripts": { "scripts": {
"verify": "python3 test-workspace-tracking.py && node test-release.cjs && git diff --check",
"release:preview": "release-it --dry-run --ci", "release:preview": "release-it --dry-run --ci",
"release:prepare": "release-it --ci", "release:prepare": "release-it --ci",
"commitlint": "commitlint" "commitlint": "commitlint"
-10
View File
@@ -1,10 +0,0 @@
const assert = require("node:assert/strict");
const { execFileSync } = require("node:child_process");
const { version } = require("../package.json");
const git = (...args) => execFileSync("git", args, { encoding: "utf8" }).trim();
assert.equal(git("status", "--porcelain"), "", "Commit all changes before preparing a release");
const tags = git("tag", "--merged", "HEAD", "--list", "v[0-9]*", "--sort=-version:refname")
.split("\n").filter(tag => /^v\d+\.\d+\.\d+$/.test(tag));
assert.equal(version, tags[0]?.slice(1) ?? "0.0.0",
"Version is already prepared or release tags are missing. Fetch tags; finish the existing release before preparing another.");
-86
View File
@@ -1,86 +0,0 @@
// Exercise the actual release command in disposable repositories, never the workspace Git.
const assert = require('node:assert/strict');
const { execFileSync, spawnSync } = require('node:child_process');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const source = path.resolve(process.argv[2] || __dirname);
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'wealthledger-release-'));
const env = { ...process.env, HUSKY: '0', NO_COLOR: '1' };
const git = (...args) => execFileSync('git', args, { cwd, env, encoding: 'utf8' }).trim();
const read = file => fs.readFileSync(path.join(cwd, file), 'utf8');
const write = (file, text) => fs.writeFileSync(path.join(cwd, file), text);
const prepare = (...args) => spawnSync(process.execPath,
[path.join(source, 'node_modules/release-it/bin/release-it.js'), '--ci', ...args],
{ cwd, env, encoding: 'utf8' });
const mustPass = result => assert.equal(result.status, 0, result.stdout + result.stderr);
const mustFail = result => assert.notEqual(result.status, 0, result.stdout + result.stderr);
const commit = message => git('commit', '--allow-empty', '-qm', message);
try {
fs.mkdirSync(path.join(cwd, 'scripts'));
fs.symlinkSync(path.join(source, 'node_modules'), path.join(cwd, 'node_modules'), 'dir');
for (const file of ['.release-it.json', 'scripts/check-release.cjs', 'commitlint.config.cjs']) {
fs.copyFileSync(path.join(source, file), path.join(cwd, file));
}
write('.gitignore', 'node_modules/\nremote.git/\n');
write('CHANGELOG.md', '# Changelog\n');
write('package.json', JSON.stringify({ name: 'release-fixture', private: true, version: '0.0.0' }));
write('package-lock.json', JSON.stringify({ name: 'release-fixture', version: '0.0.0', lockfileVersion: 3, packages: { '': { name: 'release-fixture', version: '0.0.0' } } }));
git('init', '-q', '-b', 'main');
git('config', 'user.name', 'Release test');
git('config', 'user.email', 'release@example.invalid');
git('config', 'core.hooksPath', '/dev/null');
git('init', '--bare', '-q', 'remote.git');
git('remote', 'add', 'origin', path.join(cwd, 'remote.git'));
git('add', '.');
commit('chore: initialize fixture');
mustFail(prepare('0.1.0')); // main cannot prepare releases.
git('switch', '-qc', 'release/test');
write('untracked.txt', 'must block\n');
mustFail(prepare('0.1.0'));
fs.unlinkSync(path.join(cwd, 'untracked.txt'));
const before = ['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read);
const head = git('rev-parse', 'HEAD');
mustPass(prepare('0.1.0', '--dry-run'));
assert.deepEqual(['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read), before);
assert.equal(git('rev-parse', 'HEAD'), head);
mustPass(prepare('0.1.0'));
assert.equal(git('tag', '--list'), '', 'Preparation must not create tags');
assert.equal(git('log', '-1', '--format=%s'), 'chore(release): 0.1.0');
mustFail(prepare()); // Prepared version cannot be bumped again before tagging.
git('tag', '-a', 'v0.1.0', '-m', 'Release 0.1.0');
mustFail(prepare()); // No new commits.
for (const [message, expected] of [
['fix: correct calculation', '0.1.1'],
['feat: add export', '0.2.0'],
['docs: explain export', '0.2.1'],
['spec: define export', '0.2.2'],
['chore(deps): update dependencies', '0.2.3'],
['fix!: remove legacy contract', '1.0.0'],
['feat: change export\n\nBREAKING CHANGE: replace export format', '2.0.0'],
]) {
commit(message);
mustPass(prepare());
const pkg = JSON.parse(read('package.json'));
const lock = JSON.parse(read('package-lock.json'));
assert.equal(pkg.version, expected);
assert.equal(lock.version, expected);
assert.equal(lock.packages[''].version, expected);
assert.ok(read('CHANGELOG.md').includes(expected));
assert.equal(git('status', '--porcelain'), '');
git('tag', '-a', `v${expected}`, '-m', `Release ${expected}`);
}
assert.ok(read('CHANGELOG.md').includes('/commit/'), read('CHANGELOG.md'));
assert.ok(read('CHANGELOG.md').includes('/compare/v0.1.0...v0.1.1'));
assert.ok(!read('CHANGELOG.md').includes('chore(release)'));
assert.equal(read('CHANGELOG.md').split('correct calculation').length, 2, 'Prior changes must not be repeated');
assert.equal(spawnSync('git', ['--git-dir=remote.git', 'show-ref'], { cwd }).status, 1, 'Preparation must not push refs');
const lint = message => spawnSync(process.execPath,
[path.join(source, 'node_modules/@commitlint/cli/cli.js'), '--strict'],
{ cwd, env, input: message, encoding: 'utf8' });
mustPass(lint('spec: define contract'));
mustFail(lint('unstructured message'));
console.log(`PASS: release preparation, guards, version rules, lockfile and changelog (${source})`);
} finally {
fs.rmSync(cwd, { recursive: true, force: true });
}
-77
View File
@@ -1,77 +0,0 @@
"""Check root tracking on fictional data; never stage the real workspace."""
from pathlib import Path
import subprocess
import tempfile
def check(ignore_rules):
with tempfile.TemporaryDirectory(prefix="wealthledger-tracking-") as directory:
root = Path(directory)
def git(*args):
return subprocess.check_output(
["git", "-C", str(root), *args], text=True
)
git("init", "--quiet")
allowed = {
'.ai/conventions/git.md',
'.ai/conventions/dependencies.md',
'.ai/conventions/code-health.md',
'.ai/conventions/testing.md',
'.ai/conventions/guardrails.md',
'.ai/migrations/conventions-v1/audit.md',
'.ai/migrations/conventions-v1/plan.md',
'.gitea/workflows/commits.yml',
'.gitea/workflows/security.yml',
'.gitea/workflows/verify.yml',
'.gitignore',
'.nvmrc',
'.release-it.json',
'AGENTS.md',
'CHANGELOG.md',
'README.md',
'commitlint.config.cjs',
'package-lock.json',
'package.json',
'renovate.json',
'scripts/check-release.cjs',
'test-release.cjs',
'test-workspace-tracking.py',
}
for name in allowed:
(root / name).parent.mkdir(parents=True, exist_ok=True)
(root / name).write_text("fictional fixture\n")
(root / ".gitignore").write_text(ignore_rules)
for name in ("frontend", "backend", "docs", "specs"):
nested = root / name
nested.mkdir()
subprocess.run(["git", "init", "--quiet", str(nested)], check=True)
for name in (
"frontend/source.ts", "backend/prisma/dev.db", "docs/README.md",
"specs/README.md", "statement-imports/report.csv", "private.db",
"private.db-wal", ".env", ".env.example", "audit.pdf",
"design/preview.html", "todo/notes.md", "references/source.md",
".agents/local.md", "unknown-directory/data.txt", "unknown-file.txt",
"scripts/private.txt", ".gitea/private.txt", ".gitea/workflows/private.yml",
"node_modules/private.txt",
".ai/private.md", ".ai/conventions/private.md",
".ai/migrations/private.md", ".ai/migrations/conventions-v1/private.md",
".ai/migrations/other/private.md",
):
target = root / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("fictional private data\n")
before = {p.relative_to(root): p.read_bytes() for p in root.rglob("*")
if p.is_file() and ".git" not in p.relative_to(root).parts[:1]}
git("add", ".")
assert set(git("ls-files").splitlines()) == allowed
assert all((root / p).read_bytes() == content for p, content in before.items())
assert all((root / name / ".git").is_dir()
for name in ("frontend", "backend", "docs", "specs"))
if __name__ == "__main__":
check(Path(__file__).with_name(".gitignore").read_text())
print("PASS: only root allowlist staged; fictional data and nested Git preserved")