chore: simplify root tooling and repository boundaries #10
@@ -9,9 +9,8 @@
|
||||
3. Расширь подходящий owner/helper/pattern; выбирай минимальное решение,
|
||||
полностью закрывающее текущую задачу. Новый файл сам по себе не улучшение.
|
||||
|
||||
Уменьшай количество способов решать одну техническую задачу. Во frontend
|
||||
сравни query keys, formatters, class composition; в backend — pagination,
|
||||
error mapping, repositories и обращения к одному provider. Сходный синтаксис
|
||||
Уменьшай количество способов решать одну техническую задачу: конфигурацию
|
||||
release-it и commitlint поддерживай средствами этих инструментов. Сходный синтаксис
|
||||
без общей ответственности не требует DRY. Критерий dependency — в
|
||||
[dependencies](dependencies.md).
|
||||
|
||||
@@ -23,10 +22,8 @@ Consistency с подходящим project pattern предпочтительн
|
||||
Сначала используй native API/установленный stack; не строй на гипотетических
|
||||
consumers и будущих requirements.
|
||||
|
||||
Backend: CQRS, Event Sourcing, Domain Events, Unit of Work, Command Bus,
|
||||
generic repositories, abstract factories, custom DI и elaborate domain layers
|
||||
не вводятся только ради «best practice». Frontend: wrapper над Mantine, global
|
||||
store и дополнительная OpenAPI hash/schema система не создаются автоматически.
|
||||
Не вводи shared tooling для других репозиториев как обязательную зависимость
|
||||
их сборки, CI или маршрута инструкций. Каждый repo владеет своими checks.
|
||||
Trust-boundary validation, authorization, atomicity, accessibility и необходимая
|
||||
regression proof остаются частью полного решения.
|
||||
|
||||
@@ -44,8 +41,8 @@ low-risk, behavior-preserving проблемы, которые легко про
|
||||
Устойчивый pattern должен находиться через код и manifest. Добавляй convention,
|
||||
только если следующей сессии всё ещё нужно явное project decision rule.
|
||||
У каждого правила один owner; AGENTS содержит trigger и ссылку.
|
||||
Mental model живёт в `.ai/architecture.md`, intent — в specs, системные знания —
|
||||
в docs. Audit/backlog не помещай в conventions.
|
||||
Устройство root tooling находится в README и конфигурации; их чтение не
|
||||
требует соседних checkout. Audit/backlog не помещай в conventions.
|
||||
|
||||
Legacy фиксируется отдельно; новый diff не добавляет нарушений. При migration
|
||||
закрывай подтверждённые baseline findings с evidence, сохраняя область проверки.
|
||||
@@ -65,4 +62,4 @@ Numeric policy называй по смыслу и единицам (`TIMEOUT_MS
|
||||
Индексы, `index + 1`, `Math.max(value, 0)` и обычные labels/log messages
|
||||
не выноси в constants без semantic benefit. Локальный lint/i18n profile всё равно
|
||||
применим. Не создавай tests существования констант или custom magic-string analyzer.
|
||||
Различия TS/генератора принадлежат local conventions frontend/backend.
|
||||
Правила приложений принадлежат их локальным conventions.
|
||||
|
||||
@@ -14,9 +14,6 @@ native API и зрелую небольшую dependency: parsing, serialization
|
||||
debounce, class composition. Три `Array.map` не требуют utility library.
|
||||
Не продолжай копировать boilerplate, если один принятый способ дешевле сопровождать.
|
||||
|
||||
`clsx` сейчас не принят как direct dependency. Разные способы соединить классы
|
||||
сначала требуют подтверждённого повторения; устанавливать его заранее не нужно.
|
||||
|
||||
## При выборе пакета
|
||||
|
||||
- Укажи конкретную потребность, альтернативу из текущего stack и существенную
|
||||
@@ -26,8 +23,7 @@ debounce, class composition. Три `Array.map` не требуют utility libr
|
||||
- Сохраняй exact versions и lockfile. Перед Node-командами активируй `.nvmrc`
|
||||
через `nvm use --silent`; воспроизводимая установка — `npm ci` в нужном repo.
|
||||
- Peer-конфликты решай совместимыми версиями, без `--force`/`--legacy-peer-deps`.
|
||||
Известная frontend граница TypeScript/openapi-typescript — в
|
||||
[README](../../frontend/README.md#проверка); версии принадлежат manifest/lockfile.
|
||||
Версии и compatibility принадлежат местному manifest/lockfile.
|
||||
- После изменения запускай relevant install/type/build/behavior checks и
|
||||
existing security workflow. Не добавляй второй analyzer, когда задача решается
|
||||
`npm audit` при изменении зависимостей. Не добавляй второй analyzer, когда задача решается
|
||||
настройкой существующего; [карта проверок](guardrails.md).
|
||||
|
||||
@@ -7,16 +7,14 @@
|
||||
- Commit содержит одну связанную задачу и её необходимые проверки/документацию.
|
||||
Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками.
|
||||
- Commits и заголовки PR — Conventional Commits; допустим `spec:`.
|
||||
Во frontend номер связанной issue добавляй в конец заголовка (`fix: … #12`);
|
||||
без связанной issue номер не нужен. Формат проверяет local commitlint.
|
||||
Формат проверяет local commitlint; правила других repo принадлежат их AGENTS.
|
||||
- Перед commit проверь `git diff --cached --name-status` и сам staged diff
|
||||
в каждом affected repo. Root tracking ограничен [.gitignore](../../.gitignore):
|
||||
новый общий файл получает точечный allowlist и проверку
|
||||
[tracking](../../test-workspace-tracking.py). `git add -f` не используется.
|
||||
новый общий файл получает точечный allowlist; проверь staged paths вручную. `git add -f` не используется.
|
||||
- Не выполняй `git clean -fdx`, reset чужих изменений, force-push, удаление или
|
||||
перенос опубликованных тегов как обычную очистку. Для опасной операции нужны
|
||||
явная необходимость, сохранение данных и соответствующая авторизация.
|
||||
- Обычный PR допускает squash. Release/hotfix, синхронизация main/develop,
|
||||
release-конфигурация и CI refs требуют чтения
|
||||
release-конфигурация требуют чтения
|
||||
[процесса релиза](../../README.md#релизы-и-ветки): он владеет merge policy,
|
||||
версиями и тегами. Прямой push в `main` запрещён; версии вручную не повышай.
|
||||
|
||||
@@ -1,48 +1,15 @@
|
||||
# Conventions → deterministic guardrails
|
||||
# Root проверки
|
||||
|
||||
Читай при выборе проверки или изменении tooling. Команды выполняются из repo
|
||||
владельца после `nvm use --silent`. Config/manifest задаёт фактический scope;
|
||||
таблица не заменяет semantic review и не утверждает, что все checks уже запущены.
|
||||
Root CI содержит только `commits`: штатный commitlint проверяет заголовок PR
|
||||
и коммиты между BASE и HEAD. Установка — `npm ci --ignore-scripts`, Node — из
|
||||
местной `.nvmrc`. Самописные проверки и дополнительные CI workflow не добавляются.
|
||||
|
||||
| Convention | Существующий check | Достаточность / следующий шаг |
|
||||
| --- | --- | --- |
|
||||
| Git multi-repo tracking | root `python3 test-workspace-tracking.py`, `.gitignore` | Проверяет exact allowlist и сохранность fictional data/nested Git; обновлять вместе с allowlist |
|
||||
| Conventional Commits | local commitlint + commits workflow | Syntax покрыт; атомарность и scope — review |
|
||||
| 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 |
|
||||
Локально перед Node-командами выполни `nvm use --silent`. Для изменений проверь
|
||||
JSON/config syntax, тексты, ссылки и `git diff --check`; новые файлы просмотри
|
||||
отдельно. Перед commit проверь staged paths относительно allowlist `.gitignore`.
|
||||
Зависимости устанавливай через `npm ci`; при их изменении выполни `npm audit`.
|
||||
|
||||
## Ratchet без новой инфраструктуры
|
||||
|
||||
- Existing error gates остаются обязательными; не ослабляй их ради legacy.
|
||||
- До расширения FSD rule получи candidate violations существующим depcruise.
|
||||
Его `--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.
|
||||
Release-it использует встроенные проверки ветки, чистого дерева и наличия
|
||||
коммитов. Перед подготовкой релиза вручную проверь теги и соответствие версии
|
||||
последнему принятому тегу; повторно не повышай уже подготовленную версию.
|
||||
Процесс — в [README](../../README.md#релизы-и-ветки).
|
||||
|
||||
@@ -22,5 +22,5 @@ jobs:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
printf '%s\n' "$PR_TITLE" | npm run commitlint -- --strict
|
||||
npm run commitlint -- --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
||||
printf '%s\n' "$PR_TITLE" | npm exec --no -- commitlint --strict
|
||||
npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -3,7 +3,6 @@
|
||||
!/.gitignore
|
||||
!/AGENTS.md
|
||||
!/README.md
|
||||
!/test-workspace-tracking.py
|
||||
!/.ai/
|
||||
/.ai/*
|
||||
!/.ai/conventions/
|
||||
@@ -26,14 +25,8 @@
|
||||
!/CHANGELOG.md
|
||||
!/commitlint.config.cjs
|
||||
!/renovate.json
|
||||
!/test-release.cjs
|
||||
!/scripts/
|
||||
/scripts/*
|
||||
!/scripts/check-release.cjs
|
||||
!/.gitea/
|
||||
/.gitea/*
|
||||
!/.gitea/workflows/
|
||||
/.gitea/workflows/*
|
||||
!/.gitea/workflows/commits.yml
|
||||
!/.gitea/workflows/verify.yml
|
||||
!/.gitea/workflows/security.yml
|
||||
|
||||
@@ -18,9 +18,6 @@
|
||||
"gitlab": {
|
||||
"release": false
|
||||
},
|
||||
"hooks": {
|
||||
"after:git:init": "node scripts/check-release.cjs"
|
||||
},
|
||||
"plugins": {
|
||||
"@release-it/conventional-changelog": {
|
||||
"infile": "CHANGELOG.md",
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
# WealthLedger workspace
|
||||
|
||||
`frontend`, `backend`, `docs`, `specs` и root — независимые Git-репозитории.
|
||||
Root содержит workspace-навигацию и конфигурацию стандартных инструментов.
|
||||
Самописные скрипты не добавляются; CI выполняет только commitlint.
|
||||
`frontend`, `backend`, `docs`, `specs` — самостоятельные Git-репозитории.
|
||||
Root tracking ограничен allowlist; локальные данные и nested repos в него
|
||||
не добавляются. `references/**`, dated audits и архивы — read-only.
|
||||
Структура/запуск — в [README](README.md).
|
||||
|
||||
Начни с результата задачи и affected areas. Прочитай local AGENTS затронутого
|
||||
repo; проследи поведение/инварианты и найди owner с 1–3 примерами до реализации.
|
||||
Код показывает механизм, а случайный аналог не переопределяет контракт.
|
||||
Работай напрямую; OpenSpec/SDD lifecycle нужен только по явному запросу.
|
||||
Инструкции root относятся только к root. Работа внутри другого репозитория
|
||||
начинается с его собственного AGENTS; он содержит все обязательные правила
|
||||
без наследования root conventions и без чтения соседних checkout.
|
||||
Публичные API artifacts передаются явно; CI каждого repo использует локальные
|
||||
входы. Справочные источники не становятся обязательной цепочкой инструкций.
|
||||
|
||||
| Задача / граница | Прочитать |
|
||||
Начни с результата и affected areas. До edits проследи поведение/invariants,
|
||||
найди owner и 1–3 примера. Работай напрямую; OpenSpec/SDD — по явному запросу.
|
||||
|
||||
| Root задача | Прочитать |
|
||||
| --- | --- |
|
||||
| Frontend / backend | [Frontend](frontend/AGENTS.md) / [Backend](backend/AGENTS.md), затем только активированные темы |
|
||||
| Docs / specs | [Docs AGENTS](docs/AGENTS.md) / [Specs AGENTS](specs/AGENTS.md) |
|
||||
| Новая функция, неясное ожидание, межрепозиторное поведение | [Docs entry](docs/README.md) → нужная область/spec/ADR; локальный build/lint bug начинает с repo instructions/code |
|
||||
| Новый helper/hook/wrapper/pattern, refactor, выбор scope | [Code health / change scope](.ai/conventions/code-health.md) |
|
||||
| Dependency или tooling | [Dependencies](.ai/conventions/dependencies.md), [guardrails](.ai/conventions/guardrails.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) |
|
||||
| Helper/pattern, refactor, scope | [Code health](.ai/conventions/code-health.md) |
|
||||
| Dependency/tooling | [Dependencies](.ai/conventions/dependencies.md), [Guardrails](.ai/conventions/guardrails.md) |
|
||||
| Нетривиальные tests | [Test Intent](.ai/conventions/testing.md) |
|
||||
| Git/branch/commit/PR/tracking | [Git](.ai/conventions/git.md) |
|
||||
| Release/hotfix | [README](README.md#релизы-и-ветки) |
|
||||
|
||||
Перед Node-командой активируй `.nvmrc` affected repo через `nvm use --silent`.
|
||||
Перед Node-командой активируй местную `.nvmrc`: `nvm use --silent`.
|
||||
При новой границе и перед завершением повтори routing по staged/unstaged/new
|
||||
files; сохраняй чужие изменения. Missing required source или конфликт требований
|
||||
сообщи до зависимого решения. После code diff выполни
|
||||
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review); проверь результат
|
||||
по рискам и [relevant gates](.ai/conventions/guardrails.md#выбор-финального-gate).
|
||||
Сохрани краткие команды/результаты и ограничения; успех lint не заменяет semantic review.
|
||||
files; сохраняй чужие изменения. Missing source/конфликт требований сообщи
|
||||
до зависимого решения. После diff выполни
|
||||
[Boy Scout review](.ai/conventions/code-health.md#boy-scout-review) и
|
||||
[relevant gates](.ai/conventions/guardrails.md). Запиши краткие команды,
|
||||
результаты и ограничения; lint не заменяет semantic review.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Локальное приложение для портфеля 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
|
||||
|
||||
@@ -12,10 +12,10 @@ Workspace состоит из пяти независимых Git-репозит
|
||||
| Путь от root | Репозиторий Gitea | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `.` | `WealthLedger/main` | Вход в workspace, инструкции и правила root tracking |
|
||||
| `frontend/` | `WealthLedger/frontend` | [React/Vite UI](frontend/README.md) |
|
||||
| `backend/` | `WealthLedger/backend` | [NestJS API, Prisma и SQLite](backend/README.md) |
|
||||
| `docs/` | `WealthLedger/docs` | [Объяснение системы](docs/README.md) |
|
||||
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](specs/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](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/016782657acfd35ff3fd755462007938fac35156/README.md) |
|
||||
| `docs/` | `WealthLedger/docs` | [Объяснение системы](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/README.md) |
|
||||
| `specs/` | `WealthLedger/specs` | [Требования и OpenSpec](https://git.personal.ksv741.ru/WealthLedger/specs/src/commit/9e227a563dda67620f0ad78228aa8a4c18bf35a0/README.md) |
|
||||
|
||||
### Получение checkout
|
||||
|
||||
@@ -34,13 +34,14 @@ git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/specs.git specs
|
||||
Для существующего workspace клонируйте только отсутствующие checkout в пустые
|
||||
каталоги. Проверяйте состояние каждого repo отдельно, например
|
||||
`git -C frontend status`. Обновление main не обновляет соседние repos;
|
||||
общего lockfile revisions нет. Относительные ссылки требуют указанных имён каталогов.
|
||||
общего lockfile revisions нет. Такая раскладка удобна для совместной работы,
|
||||
но каждый checkout можно использовать отдельно.
|
||||
|
||||
### Что хранится в main
|
||||
|
||||
Root отслеживает входные документы, общие engineering conventions и их migration
|
||||
Root отслеживает входные документы, собственные engineering conventions и исторический migration
|
||||
inventory в `.ai/`, npm/release-конфигурацию, changelog,
|
||||
проверки tracking/release и три CI workflow. Точный allowlist — в `.gitignore`.
|
||||
один CI workflow с commitlint. Точный allowlist — в `.gitignore`.
|
||||
Остальные root-пути игнорируются по умолчанию:
|
||||
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
|
||||
`design/`, `todo/` и локальные настройки инструментов.
|
||||
@@ -61,7 +62,7 @@ inventory в `.ai/`, npm/release-конфигурацию, changelog,
|
||||
После смены major Node переустановите native dependencies через `npm ci`.
|
||||
Разрешения 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`.
|
||||
Существующий `.env` сохраняйте. Из backend:
|
||||
|
||||
@@ -74,13 +75,13 @@ npm run start:dev
|
||||
|
||||
Build генерирует Prisma client. Перед миграцией существующей базы сохраните
|
||||
согласованный backup и ключ шифрования; процедуру и ограничения отката смотрите
|
||||
в [описании хранения](docs/architecture/containers.md#сохранение-и-восстановление-данных).
|
||||
в [описании хранения](https://git.personal.ksv741.ru/WealthLedger/docs/src/commit/c557af4ea51cd3c578d0755e909e62e06462b5bc/architecture/containers.md#сохранение-и-восстановление-данных).
|
||||
Известная ошибка fresh migration при `RUST_LOG=warn/error` устранена в
|
||||
`backend/prisma.config.ts`: CLI удаляет унаследованную переменную.
|
||||
Используйте штатный `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:
|
||||
|
||||
```bash
|
||||
@@ -99,7 +100,7 @@ npm run dev
|
||||
OpenSpec не нужен для запуска приложения и применяется по явному запросу.
|
||||
Specs использует Node из своей `.nvmrc`; проверенная версия CLI — 1.10.0.
|
||||
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 |
|
||||
| frontend | `npm run verify` | Типы, lint, тесты, зависимости, OpenAPI snapshot и сборки |
|
||||
| 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 |
|
||||
|
||||
Python 3 нужен только для проверки root tracking. Backend e2e использует
|
||||
Root CI выполняет только commitlint: проверяет сообщения коммитов и заголовок PR.
|
||||
Allowlist root проверяется вручную по staged paths. Backend e2e использует
|
||||
временные базы; verify отдельно не доказывает HTTP/DB-сценарии или доступность
|
||||
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).
|
||||
|
||||
## Релизы и ветки
|
||||
@@ -144,18 +146,19 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
||||
1. Обновить `main`, `develop` и теги командой `git fetch origin --tags`.
|
||||
Проверить, что текущий `develop` содержит последний релиз из `main`.
|
||||
Создать `release/<name>` от проверенного `develop` и выполнить `npm ci`.
|
||||
2. Для backend и docs обновить `.ci/refs.json`: полные SHA совместимых соседних
|
||||
репозиториев. Эти revisions должны быть доступны в Gitea. Проверить именно
|
||||
их совместимость; движущиеся ветки не являются фиксацией состава релиза.
|
||||
Закоммитить обновлённые pins до подготовки версии.
|
||||
2. Проверить локальные входы и публичные artifacts выпускаемого репозитория.
|
||||
Совместимость развёрнутых участников согласовать отдельно через публичные
|
||||
контракты; CI не скачивает соседние checkout и не требует их SHA.
|
||||
3. Выполнить `npm run release:preview`. Проверить номер и changelog, затем
|
||||
`npm run release:prepare`. Команда создаёт `chore(release): X.Y.Z`, обновляет
|
||||
`package.json`, lockfile и `CHANGELOG.md`; теги и push не выполняет.
|
||||
Не использовать обход branch/clean/tag guards. Незакоммиченные файлы,
|
||||
неподходящая ветка, отсутствие изменений и уже подготовленная версия блокируют выпуск.
|
||||
В root встроенные проверки release-it контролируют ветку, чистое дерево и
|
||||
наличие коммитов. Теги и соответствие версии последнему принятому тегу
|
||||
проверяются вручную; уже подготовленную версию повторно не повышать.
|
||||
4. Проверить diff релизного коммита и штатные gates. Отправить только релизную
|
||||
ветку: `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` изменился, сначала
|
||||
согласовать историю и повторить проверку кандидата; не обходить защиту.
|
||||
5. После merge получить `main` и убедиться, что его HEAD — проверенный релизный
|
||||
@@ -185,25 +188,26 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
|
||||
`release/hotfix-<name>` в `main`, затем обязательно возвращается в `develop`.
|
||||
Если ломается публичный контракт, major сохраняется и для hotfix.
|
||||
|
||||
Backend CI проверяет runtime OpenAPI с frontend; docs CI — межрепозиторные
|
||||
ссылки. Для `main`, PR в `main` и `release/*` соседи берутся из `.ci/refs.json`;
|
||||
для разработки — из `develop`. SHA записываются в логи. Для изменения сразу
|
||||
нескольких репозиториев сначала согласовать их revisions, затем проверять
|
||||
релизный набор; порядок публикации сам по себе не доказывает совместимость.
|
||||
Каждый CI использует только свой checkout. Backend генерирует OpenAPI без сервера,
|
||||
сверяет генератор с configured app и не хранит JSON в Git; frontend проверяет types по входному
|
||||
snapshot. Docs проверяет внутренние ссылки; внешние источники даются как
|
||||
справочные веб-ссылки. Обновление API artifact потребителя — отдельное действие.
|
||||
Зелёные локальные gates и порядок публикации не доказывают совместимость
|
||||
развёрнутых версий. Процедуры выпуска находятся также в README каждого repo;
|
||||
доступ к root для выпуска другого репозитория не требуется.
|
||||
|
||||
В Gitea для `main` и `develop` нужны запрет прямого push и force-push,
|
||||
обязательные проверки `verify`, `security`, `commits` и запрет обхода правил
|
||||
обязательные проверки: в root только `commits`, в остальных repo —
|
||||
`verify`, `security`, `commits`; также нужен запрет обхода правил
|
||||
администратором. Требование отдельного одобрения review не вводится для
|
||||
разработки одним владельцем. Ветка по умолчанию — `develop`; Renovate тоже
|
||||
отправляет PR в `develop`. Теги `v*` защищены, создание разрешается владельцу
|
||||
релиза; их удаление или перенос не входит в обычный процесс.
|
||||
|
||||
`node test-release.cjs` из root проверяет настоящий release-it во временном Git.
|
||||
Для проверки конфигурации соседа после установки его dependencies:
|
||||
`node test-release.cjs frontend` (аналогично backend, docs, specs).
|
||||
Тест не публикует refs и не использует рабочие базы. Root `npm run verify`
|
||||
включает tracking и этот тест; frontend/backend/docs сохраняют свои gates,
|
||||
backend дополнительно требует `npm run test:e2e`.
|
||||
В root нет собственного test/verify runner и release hooks. Release-it и
|
||||
commitlint используются напрямую. Остальные репозитории сохраняют свои gates.
|
||||
Если в защите веток root ранее были обязательными `verify` и `security`, их
|
||||
нужно убрать из required checks в Gitea; YAML не меняет настройки сервера.
|
||||
|
||||
Npm publishing, автоматический deploy и карточки Gitea Releases не включены.
|
||||
Граница выпуска — принятый коммит, changelog и Git-тег.
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"node": "24.21.0"
|
||||
},
|
||||
"scripts": {
|
||||
"verify": "python3 test-workspace-tracking.py && node test-release.cjs && git diff --check",
|
||||
"release:preview": "release-it --dry-run --ci",
|
||||
"release:prepare": "release-it --ci",
|
||||
"commitlint": "commitlint"
|
||||
|
||||
@@ -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.");
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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")
|
||||
Reference in New Issue
Block a user