17 Commits
Author SHA1 Message Date
ksv741 3027fd51ec ci: validate release PR commits independently (WealthLedger/main#30)
Conventional commits / commits (pull_request) Successful in 11s
2026-10-12 23:39:30 +03:00
ksv741 a659c9dcc7 chore(release): 0.2.2
Conventional commits / commits (pull_request) Failing after 11s
2026-10-12 23:19:12 +03:00
ksv741 d2d1092344 Merge pull request 'docs: refine workflow conventions after review (#27)' (#29) from docs/27-workflow-review-fixes into develop
Reviewed-on: #29
2026-10-12 22:00:20 +03:00
ksv741 fc589c9130 docs: refine workflow conventions after review (#27)
Conventional commits / commits (pull_request) Successful in 12s
2026-10-12 20:29:00 +03:00
codex-bot 3321d9db25 Merge pull request 'chore: establish engineering workflow (#27)' (#28) from chore/27-engineering-workflow into develop 2026-10-12 11:00:35 +03:00
ksv741 03b3b59e6f fix: align workflow commitlint policy (#27)
Conventional commits / commits (pull_request) Successful in 13s
2026-10-12 10:17:53 +03:00
ksv741 59e0831ad2 docs: document PR ownership and board stage (#27)
Conventional commits / commits (pull_request) Successful in 10s
2026-10-12 09:45:41 +03:00
ksv741 2dc72255fe chore: establish engineering workflow (#27)
Conventional commits / commits (pull_request) Successful in 10s
2026-10-12 09:19:58 +03:00
codex-bot ed8221ae8a Merge pull request 'docs: clarify root Node bootstrap #24' (#26) from codex/issue-24-process into develop 2026-10-11 23:23:10 +03:00
ksv741 1ed4dd5a73 docs: clarify root Node bootstrap #24
Conventional commits / commits (pull_request) Successful in 13s
2026-10-11 23:06:51 +03:00
ksv741 dc030f84c9 chore(release): 0.2.1
Conventional commits / commits (pull_request) Successful in 11s
2026-10-07 18:13:55 +03:00
ksv741 f27487b6a8 fix(deps): refresh release security baseline
Conventional commits / commits (pull_request) Successful in 11s
2026-10-07 15:36:45 +03:00
ksv741 9fbc23f757 docs: link production deployment repository
Conventional commits / commits (pull_request) Successful in 13s
2026-09-20 18:50:05 +03:00
ksv741 80fa254a31 chore: point container images to Nexus #15
Conventional commits / commits (pull_request) Successful in 10s
2026-09-17 20:41:10 +03:00
ksv741 21853fde6a chore(release): 0.2.0
Conventional commits / commits (pull_request) Successful in 13s
2026-09-16 11:34:27 +03:00
ksv741 7e382d460b feat: add containerized application delivery #11
Conventional commits / commits (pull_request) Successful in 10s
2026-09-16 09:42:13 +03:00
ksv741 95e4361599 chore: simplify root tooling and repository boundaries
Conventional commits / commits (pull_request) Successful in 10s
2026-09-15 20:45:36 +03:00
29 changed files with 574 additions and 379 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).
+162
View File
@@ -0,0 +1,162 @@
# Engineering Workflow
Область: пять независимых checkout workspace (`main`, `frontend`, `backend`,
`docs`, `specs`). Репозиторий `deploy` существует в Gitea отдельно и применяет
свои локальные правила. Этот документ — единственный владелец общего процесса;
локальные AGENTS и conventions определяют технические проверки и ограничения.
## Issue Management
| Переход | Условие входа → действие → условие выхода |
| --- | --- |
| Request → Issue | До изменения файлов/конфигурации/спецификаций определить затронутые repos, найти по смыслу открытые и закрытые Issues, проверить актуальность и scope. Взять подходящую открытую Issue (закрытую переоткрыть только для действительно незавершённой работы) либо создать одну новую. Чтение, анализ и предложение изменений разрешены без Issue. |
| Issue → Analysis | В Issue должны быть результат в title, контекст/проблема и ожидаемое поведение, проверяемые Acceptance Criteria. Уточнить неизвестное до зависимого решения. Выбрать тип и приоритет; назначить исполнителя, если известен; milestone только при реальном плане, dependencies/related Issues только при наличии. |
| Analysis → Delivery | Определить область и риски, выбрать lightweight или SDD ниже. Если AC или owner меняются, сначала обновить соответствующую Issue/согласование. |
Типы: `feature`, `bug`, `refactor`, `tech-debt`, `docs`, `test`, `ci`,
`chore`, `performance`. Существующие org labels отражают их без требования
один-к-одному: `Kind/Feature`, `Kind/Bug`, `Kind/Refactor`,
`Kind/Documentation`, `Kind/Testing`, `Kind/Enhancement` (performance);
repo-local `Kind/Process` подходит для ci, chore и process/tech-debt.
Дополнительные `Kind/Security`, `Kind/Investigation`, `Kind/Dependencies`
(последние два — repo-local) — по ситуации. Priority: существующие
`Priority/Critical|High|Medium|Low`.
Gitea assignee, milestone и state не дублируются labels. Для назначаемых
Issue и PR выбирай `hermes-agent`; незанятую задачу не назначай формально.
Шаблоны ускоряют ввод, но не доказывают качество заполнения.
Один координирующий Issue достаточен, если результат и AC едины, одна команда
может принять работу целиком и отдельные repos не требуют независимой поставки.
Иначе Issue в каждом независимо принимаемом repo; ссылаться друг на друга и
указать порядок зависимостей. Номера `#27` локальны: для чужого repo использовать
`WealthLedger/main#27` или полный URL. Не заводить дубль только из-за второго PR.
## Git Branching
| Переход | Условие входа → действие → условие выхода |
| --- | --- |
| Analysis → Branch | Issue и AC определены; проверить `git status`, текущую ветку и `origin/develop`. Использовать существующую подходящую ветку либо создать одну от актуального `develop` каждого affected repo. Чужие изменения не переносить. |
| Branch → Implementation | `<type>/<issue-number>-<english-kebab-description>`; номер принадлежит указанной в PR Issue (для cross-repo — координатору). Примеры: `feature/123-import-transactions`, `fix/124-invalid-balance`, `refactor/125-simplify-api-client`, `docs/126-update-architecture`. |
Префиксы: feature → `feature`, bug → `fix`, refactor/tech-debt →
`refactor`, docs → `docs`, test → `test`, ci → `ci`, chore → `chore`,
performance → `perf`. Номер обязателен для новых рабочих веток; короткий
английский slug в kebab-case. Ветки `main`/`develop` не место для прямой
разработки. Release/hotfix, обратная синхронизация, Renovate и исторические
ветки подчиняются существующему release/automation процессу, а не переименовываются.
## Commit Conventions
| Переход | Условие входа → действие → условие выхода |
| --- | --- |
| Implementation → Commit | Одна связанная задача на commit, проверить staged paths/diff. Заголовок Conventional Commits с осмысленным subject и ссылкой на Issue: `feat(portfolio): add transaction import (#123)` или `ci: align gates (WealthLedger/main#27)`. Scope только при полезной области. |
Commitlint требует распознаваемую conventional-commits-parser ссылку, но не
проверяет числовой формат, существование Issue, её смысл или корректный repo.
Использовать `#N` только для локальной Issue и `WealthLedger/repo#N` для чужой;
это формат команды, проверяемый при review. Не добавлять custom validator
ради более строгой регулярной проверки. Без `fixes`,
`closes`, `resolves` в сообщении: не закрывать задачу первым коммитом.
Ссылка в footer тоже принимается commitlint; размещение в заголовке остаётся
правилом команды. Если заголовок PR после squash станет commit title, оставить
в нём ссылку. `chore(release): X.Y.Z` освобождён только при точном SemVer
заголовке release-it. Стандартные Gitea merge commits освобождаются по точному
шаблону merge message; sync PR используют обычный review/merge process.
Шаблонные сообщения `Revert`/`fixup!`/`squash!` не освобождаются: revert
оформляй как `revert: … (#N)`. Историю не переписывать ради формата.
## Pull Request Process
| Переход | Условие входа → действие → условие выхода |
| --- | --- |
| Validation → PR | Завершить AC и релевантные локальные проверки; PR в `develop` из рабочей ветки, только связанный diff. В теле: Summary, Linked Issue, Changes, Verification (команда/результат/ограничения), Breaking Changes при наличии. Для cross-repo PR указывать полный reference и связанные PR. |
| PR → Review → Merge | Дождаться required checks на финальной ревизии, проверить AC, значимые изменения — review владельца/специалиста. При замечаниях исправить и обновить evidence. Merge только по политике repo. |
| Merge → Issue Closed | Закрыть Issue после принятия **всех** связанных delivery areas, документации и при SDD — обязательной verification/archive; multi-PR Issue остаётся открытой до последнего результата. |
Перед передачей PR на review перенеси в него тип и приоритет связанной Issue,
назначь `hermes-agent`, привяжи к подходящему milestone (если он есть) и к
`WealthLedger — Delivery Board`, колонка «На проверке». Для cross-repo PR
используй milestone его собственного repo. Колонку меняй вместе со стадией:
при блокировке — «Заблокировано», после принятия — «Готово». Не вводи label
этапа вместо встроенной колонки проекта.
Для SDD отдельные pre-archive checks/semantic review выполняются до archive по
локальной schema; порядок merge/archive согласуется с конкретным change, но
Issue закрывается только после обоих событий. Не подменять OpenSpec approval
обычным PR review.
## SDD Integration
Lightweight: локальный bugfix с известным ожидаемым поведением, docs-only,
рефакторинг без изменения контрактов/наблюдаемого поведения и малые CI-правки:
`Issue → Analysis → Branch → Implementation → Validation → PR`.
SDD: новая функциональность, изменение публичного API, бизнес-правил, поведения
нескольких компонентов, архитектуры или неоднозначные требования. После Issue
уточнить намерение и запросить согласование Change Owner на вход в OpenSpec;
OpenSpec lifecycle применяется только по явному запросу/решению владельца.
Использовать **только** store `wealth-ledger-specs` и его локальный lifecycle:
Proposal → Delta Specs → System Analysis → Design + QA Analysis → Docs Impact →
Tasks → Apply (для legacy change — его собственная schema). Явные approvals
перед downstream artifacts и Apply не заменяются Issue/PR. Ветка specs может
появиться для planning; ветки реализации — когда Apply разрешён, без
насильственной общей точки создания всех веток. Связать Issue ↔ change ID ↔
PR текстовыми ссылками; после проверки применить штатные sync/archive и
completion evidence согласно локальной schema. Число файлов не определяет SDD.
Если владелец не согласовал вход в SDD, не начинать зависящую реализацию
контракта/семантики и явно сообщить блокировку.
## AI Agent Responsibilities
Перед edit — поиск подходящей Issue, AC, workflow, branch/status каждого repo;
затем только согласованный scope. По завершении — итоговый diff, проверенные
команды и ограничения, подготовка commit/PR по запросу и статус Issue. Чтение
не требует Issue. Если Gitea недоступна, сообщить об этом до edit и запросить
issue reference; не выдавать локальную заметку за Issue. Агенты используют
локальные инструкции репозитория и его CI inputs, не полагаются на соседние
checkout как обязательную build-зависимость. Этот источник доступен в main;
при недоступной ссылке самостоятельный checkout сохраняет обязательный минимум
в своём AGENTS и не блокирует локальную работу из-за отсутствия root checkout.
## Quality Gates
Итерации: узкие проверки по риску; после логического изменения — релевантные
тесты; перед передачей результата — локальные обязательные gates данного repo
на итоговом diff; CI — независимые полные проверки. Не повторять дорогой verify
после каждого файла; повторить при изменении значимых входов. `main`: `commits`;
остальные: локальные `verify`, `security`, `commits` согласно фактическим CI.
Frontend/backend запускают их как jobs в едином `verify.yml`, docs/specs —
как отдельные workflows; второго CI ради одного правила не требуется.
Для Markdown-only не запускать application build. Результат lint не заменяет
семантическую проверку. Обязательность merge checks зависит от защиты ветки.
| Правило | Уровень на момент принятия документа |
| --- | --- |
| Conventional Commits и parser-recognized Issue reference в PR title/commits | CI-проверка через существующий commitlint; блокировка merge **только если** check обязателен в branch protection. Не гарантирует numeric reference, существование или соответствие Issue. Точный release SemVer title и штатные Gitea merge messages исключены. Локальные hooks не гарантируют server-side. |
| PR вместо прямого push; successful checks перед merge | Enforced только в repos/ветках, где Gitea branch protection настроена и проверена владельцем. YAML сам по себе не защищает ветку. |
| Поиск актуальной Issue, AC, корректный repo, branch name/source, соответствие ссылки, SDD selection, scope, статус | Agent-guided; templates и текст не могут гарантировать соответствие. |
| Значимое review, соответствие AC/контракту, исключения и closure после всех PR | Manual; approval может быть required в Gitea, если есть независимый reviewer. |
## Exceptions and Recovery
Срочный hotfix: немедленно завести/найти Issue, затем исправление в
`release/hotfix-<name>` от актуального `main` по существующему release process;
проверки и PR в `main`, затем вернуть результат в `develop`. Связать Issue
в PR и сопровождающем коммите; специальная release-ветка исключена из обычного
шаблона имён. Не отменять Issue или проверки из-за срочности.
Если CI красный или PR устарел — исправить в той же ветке и перепроверить
актуальный HEAD; если AC изменились — обновить Issue и при SDD вернуть change
к затронутому approval gate. При недоступной Gitea продолжать только анализ.
Изменение PR title после зелёного CI: проверить, что check пересчитан;
если `edited` trigger у существующих checks отсутствует, reviewer проверяет
текущий title вручную. Новые правила не применяются к историческим коммитам
и автоматической обратной синхронизации релиза.
Примеры: фича с новым поведением — Issue → согласованный OpenSpec change →
ветки Apply → PR каждого affected repo → archive → закрытие Issue. Локальный
bugfix — Issue → `fix/124-invalid-balance` → регрессия → PR → закрытие.
Cross-repo контракт — одна Issue с общими AC, `backend` и `frontend` PR с
`WealthLedger/main#27` и взаимными ссылками; если независимые acceptance/owners,
отдельные связанные Issues. Рефакторинг без изменения поведения — lightweight
`refactor/125-simplify-api-client`. Hotfix — Issue → `release/hotfix-*` от
`main` → PR/release → back-merge в `develop` → закрытие.
+7 -7
View File
@@ -2,21 +2,21 @@
- Выполняй Git-команды из репозитория владельца: root, frontend, backend, - Выполняй Git-команды из репозитория владельца: root, frontend, backend,
docs и specs имеют независимые history, index и branches. docs и specs имеют независимые history, index и branches.
- Новая работа: ветка `codex/<task>` от актуального `develop`, затем PR в `develop`. - Новая работа: ветка с номером Issue по [Engineering Workflow](engineering-workflow.md#git-branching)
от актуального `develop`, затем PR в `develop`.
Сохраняй чужие staged/unstaged/untracked изменения; не переноси их в свой commit. Сохраняй чужие staged/unstaged/untracked изменения; не переноси их в свой commit.
- Commit содержит одну связанную задачу и её необходимые проверки/документацию. - Commit содержит одну связанную задачу и её необходимые проверки/документацию.
Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками. Межрепозиторное изменение оформляй отдельными commits/PR с взаимными ссылками.
- Commits и заголовки PR — Conventional Commits; допустим `spec:`. - Commits и заголовки PR — Conventional Commits со ссылкой на Issue;
Во frontend номер связанной issue добавляй в конец заголовка (`fix: … #12`); допустим `spec:`. Исключения release — в README.
без связанной issue номер не нужен. Формат проверяет local commitlint. Формат проверяет local commitlint; правила других repo принадлежат их AGENTS.
- Перед 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.
+12
View File
@@ -0,0 +1,12 @@
# Copy to a restricted, untracked file. Never commit filled values.
# Full release image references; use published version tags, not latest.
FRONTEND_IMAGE=nexus.ksv741.keenetic.pro/wealthledger/frontend:0.2.0
BACKEND_IMAGE=nexus.ksv741.keenetic.pro/wealthledger/backend:0.2.0
FRONTEND_PORT=8080
FRONTEND_ORIGIN=https://wealthledger.example.com
# Absolute directory, pre-created with owner UID/GID 1000:1000 and mode 0700.
DATA_DIRECTORY=/srv/wealthledger/data
# Generate once and keep across updates. Store an independent key backup.
JWT_SECRET=
TOKEN_ENCRYPTION_KEY=
LOG_LEVEL=info
+10
View File
@@ -0,0 +1,10 @@
# Local-only fixture values. Never use this file for a server deployment.
# Base-file interpolation happens before overrides; these references are replaced.
FRONTEND_IMAGE=wealthledger-frontend:development
BACKEND_IMAGE=wealthledger-backend:development
DATA_DIRECTORY=/unused-development-volume
FRONTEND_PORT=5173
FRONTEND_ORIGIN=http://localhost:5173
JWT_SECRET=local-development-only-not-a-server-secret
TOKEN_ENCRYPTION_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=
LOG_LEVEL=info
+12
View File
@@ -0,0 +1,12 @@
---
name: Bug Report
about: Неверное поведение
---
## Проблема и шаги воспроизведения
## Факт / ожидание
## Acceptance Criteria
- [ ]
## Окружение / ссылки (если применимо)
+10
View File
@@ -0,0 +1,10 @@
---
name: Engineering Task
about: CI, тесты, документация, процесс и прочее
---
## Контекст и результат
## Scope / связанные Issues (если есть)
## Acceptance Criteria
- [ ]
+12
View File
@@ -0,0 +1,12 @@
---
name: Feature Request
about: Новое наблюдаемое поведение
---
## Зачем и какой результат
## Ожидаемое поведение
## Acceptance Criteria
- [ ]
## Зависимости / связанные Issues (если есть)
+12
View File
@@ -0,0 +1,12 @@
---
name: Refactoring / Technical Debt
about: Улучшение без нового поведения
---
## Проблема и результат
## Сохраняемые инварианты / границы
## Acceptance Criteria
- [ ]
## Зависимости / ссылки (если есть)
+11
View File
@@ -0,0 +1,11 @@
## Summary
## Linked Issue
Refs
## Changes
## Verification
- Команда / результат / ограничения:
## Breaking Changes (если есть)
+6 -2
View File
@@ -22,5 +22,9 @@ 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" if [[ "$PR_TITLE" =~ ^chore\(release\):\ [0-9]+\.[0-9]+\.[0-9]+$ ]]; then
npm exec --no -- commitlint --strict --from "$HEAD_SHA^" --to "$HEAD_SHA"
else
npm exec --no -- commitlint --strict --from "$BASE_SHA" --to "$HEAD_SHA"
fi
-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
+13 -7
View File
@@ -3,12 +3,16 @@
!/.gitignore !/.gitignore
!/AGENTS.md !/AGENTS.md
!/README.md !/README.md
!/test-workspace-tracking.py !/compose.yaml
!/compose.development.yaml
!/.env.compose.example
!/.env.development.example
!/.ai/ !/.ai/
/.ai/* /.ai/*
!/.ai/conventions/ !/.ai/conventions/
/.ai/conventions/* /.ai/conventions/*
!/.ai/conventions/git.md !/.ai/conventions/git.md
!/.ai/conventions/engineering-workflow.md
!/.ai/conventions/dependencies.md !/.ai/conventions/dependencies.md
!/.ai/conventions/code-health.md !/.ai/conventions/code-health.md
!/.ai/conventions/testing.md !/.ai/conventions/testing.md
@@ -25,15 +29,17 @@
!/.release-it.json !/.release-it.json
!/CHANGELOG.md !/CHANGELOG.md
!/commitlint.config.cjs !/commitlint.config.cjs
!/.gitleaksignore
!/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/ISSUE_TEMPLATE/
!/.gitea/workflows/security.yml /.gitea/ISSUE_TEMPLATE/*
!/.gitea/ISSUE_TEMPLATE/feature.md
!/.gitea/ISSUE_TEMPLATE/bug.md
!/.gitea/ISSUE_TEMPLATE/refactor.md
!/.gitea/ISSUE_TEMPLATE/engineering.md
!/.gitea/PULL_REQUEST_TEMPLATE.md
+3
View File
@@ -0,0 +1,3 @@
# Reviewed historical local-development fixture: base64 of the deterministic
# string 0123456789abcdef repeated twice, never a runtime secret.
7e382d460be296b6c88e17084fff17bb7728f6ea:.env.development.example:generic-api-key:9
-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",
+31 -22
View File
@@ -1,31 +1,40 @@
# 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 использует локальные
входы. Справочные источники не становятся обязательной цепочкой инструкций.
| Задача / граница | Прочитать | До изменения проекта найди подходящую Gitea Issue (или создай после поиска),
проверь Acceptance Criteria, scope и рабочую ветку. Чтение/анализ допускаются
без Issue. Маршрут, выбор lightweight/SDD и cross-repo ссылки — в
[Engineering Workflow](.ai/conventions/engineering-workflow.md).
Начни с результата и 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` не загружен
в non-interactive shell, загрузи установленный `$HOME/.nvm/nvm.sh`, затем
выполни `nvm use --silent` из root. Если `nvm.sh` недоступен, остановись;
не используй системный Node вместо проектного.
При новой границе и перед завершением повтори 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.
+45
View File
@@ -1,5 +1,50 @@
# Changelog # Changelog
## [0.2.2](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.2.1...v0.2.2) (2026-10-12)
### Bug Fixes
* align workflow commitlint policy ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([03b3b59](https://git.personal.ksv741.ru/WealthLedger/main/commit/03b3b59e6ff2ba391f9dfc42a039a96d0d84c1c9))
### Documentation
* clarify root Node bootstrap [#24](https://git.personal.ksv741.ru/WealthLedger/main/issues/24) ([1ed4dd5](https://git.personal.ksv741.ru/WealthLedger/main/commit/1ed4dd5a73a250f24f452a26b5f5b0fcbb15f758))
* document PR ownership and board stage ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([59e0831](https://git.personal.ksv741.ru/WealthLedger/main/commit/59e0831ad28d6990416e651f55d783b8d2ac98c2))
* refine workflow conventions after review ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([fc589c9](https://git.personal.ksv741.ru/WealthLedger/main/commit/fc589c9130e90480c09f8fe8d47dd871aa05145e))
### Maintenance
* establish engineering workflow ([#27](https://git.personal.ksv741.ru/WealthLedger/main/issues/27)) ([2dc7225](https://git.personal.ksv741.ru/WealthLedger/main/commit/2dc72255fe51bb48a208e10846d42d9f59e2c1af))
## [0.2.1](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.2.0...v0.2.1) (2026-10-07)
### Bug Fixes
* **deps:** refresh release security baseline ([f27487b](https://git.personal.ksv741.ru/WealthLedger/main/commit/f27487b6a8cb59243a34e392059010dab38efd51))
### Documentation
* link production deployment repository ([9fbc23f](https://git.personal.ksv741.ru/WealthLedger/main/commit/9fbc23f7570ecb6ea13f7fae5490504d1a0d9ba8))
### Maintenance
* point container images to Nexus [#15](https://git.personal.ksv741.ru/WealthLedger/main/issues/15) ([80fa254](https://git.personal.ksv741.ru/WealthLedger/main/commit/80fa254a31d525dbc98bb3fe2d949f78c8e5872b))
## [0.2.0](https://git.personal.ksv741.ru/WealthLedger/main/compare/v0.1.0...v0.2.0) (2026-09-16)
### Features
* add containerized application delivery [#11](https://git.personal.ksv741.ru/WealthLedger/main/issues/11) ([7e382d4](https://git.personal.ksv741.ru/WealthLedger/main/commit/7e382d460be296b6c88e17084fff17bb7728f6ea))
### Documentation
* align inventory with simplified conventions ([4a78dd6](https://git.personal.ksv741.ru/WealthLedger/main/commit/4a78dd640d4a1db1b7c9dc4036375c076d55b34d))
* establish workspace engineering conventions ([93dd553](https://git.personal.ksv741.ru/WealthLedger/main/commit/93dd5539ad8bd10e11c59708e7b66affc19e4883))
### Maintenance
* simplify root tooling and repository boundaries ([95e4361](https://git.personal.ksv741.ru/WealthLedger/main/commit/95e4361599244598126394aa658a0a27d77c0686))
## 0.1.0 (2026-09-14) ## 0.1.0 (2026-09-14)
### Documentation ### Documentation
+73 -39
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`. Compose configuration, безопасные env examples и один CI workflow с commitlint. Точный allowlist — в `.gitignore`.
Остальные root-пути игнорируются по умолчанию: Остальные root-пути игнорируются по умолчанию:
вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`, вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`,
`design/`, `todo/` и локальные настройки инструментов. `design/`, `todo/` и локальные настройки инструментов.
@@ -52,6 +53,34 @@ inventory в `.ai/`, npm/release-конфигурацию, changelog,
Не запускайте очистку workspace через `git clean -fdx`: ignored-пути содержат Не запускайте очистку workspace через `git clean -fdx`: ignored-пути содержат
локальные данные. Правила tracking не удаляют файлы и вложенные `.git`. локальные данные. Правила tracking не удаляют файлы и вложенные `.git`.
## Запуск через Docker Compose
Нужны Docker Engine и Compose >= 2.24.4, а для development — checkout
`frontend/` и `backend/` рядом с root. Компоненты собираются независимо:
```bash
docker compose --env-file .env.development.example \
-f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180
```
Откройте `http://localhost:5173`: Vite HMR и `/api/` используют один origin,
Nest работает в watch mode. Host `node_modules` и `.env` не монтируются.
Данные development хранятся отдельно в named volume `development-data` проекта `wealthledger-dev`;
обычные restart/down их сохраняют. Изменение dependencies/config требует rebuild.
Production использует отдельный [репозиторий deploy](https://git.personal.ksv741.ru/WealthLedger/deploy):
его Git-тег закрепляет пару images, а `./deploy.sh vX.Y.Z` выполняет обновление.
Рабочие данные, сертификат T-Bank и постоянные ключи находятся вне checkout.
Только frontend публикует `127.0.0.1:8080`; серверный Caddy направляет туда весь
домен. Backend port остаётся внутренним. Корневой Compose сохраняется для development.
Пошаговая процедура установки, обновления и восстановления — README `deploy`;
архитектурные границы — [документация](https://git.personal.ksv741.ru/WealthLedger/docs),
страница `architecture/deployment.md`.
Перед backend update обязательны остановка, backup и успешная migration.
Image rollback не восстанавливает старую базу. Публикация component images
принадлежит их Gitea workflows; root CI остаётся только commitlint.
## Подготовка и запуск ## Подготовка и запуск
Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19). Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19).
@@ -61,7 +90,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 +103,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 +128,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,18 +136,20 @@ 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).
Общий маршрут Issue → PR — [Engineering Workflow](.ai/conventions/engineering-workflow.md).
## Релизы и ветки ## Релизы и ветки
@@ -127,8 +158,8 @@ T-Bank. Полные команды и ограничения принадлеж
`vX.Y.Z`. Root-репозиторий называется `main`, его ветки — `main` и `develop`. `vX.Y.Z`. Root-репозиторий называется `main`, его ветки — `main` и `develop`.
`main` хранит принятые релизы, `develop` — интеграцию разработки. Новая задача: `main` хранит принятые релизы, `develop` — интеграцию разработки. Новая задача:
отдельная ветка от свежего `develop`, затем PR в `develop`. Для веток агента отдельная [issue-linked ветка](.ai/conventions/engineering-workflow.md#git-branching)
используется `codex/<task>`; также допустимы `feature/<task>` и `fix/<task>`. от свежего `develop`, затем PR в `develop`.
В `main` напрямую не пушить. Обычные PR можно squash-merge; релизные PR и В `main` напрямую не пушить. Обычные PR можно squash-merge; релизные PR и
обратную синхронизацию `main` с `develop` сливать с сохранением истории. обратную синхронизацию `main` с `develop` сливать с сохранением истории.
@@ -137,25 +168,27 @@ Commits и заголовки PR — Conventional Commits. `feat:` повыша
`docs:`, `spec:`, `chore:`, `build:`, `ci:`, `refactor:`, `test:`, `style:` и `docs:`, `spec:`, `chore:`, `build:`, `ci:`, `refactor:`, `test:`, `style:` и
`revert:` дают patch при выпуске. `chore(release):` — служебная запись, `revert:` дают patch при выпуске. `chore(release):` — служебная запись,
исключённая из changelog и рекомендации версии. Release-it не запускается исключённая из changelog и рекомендации версии. Release-it не запускается
автоматически после каждого commit. Номер связанной issue можно добавить в заголовок. автоматически после каждого commit. Ссылка на Issue — в
[Commit Conventions](.ai/conventions/engineering-workflow.md#commit-conventions).
### Подготовка релиза ### Подготовка релиза
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 +218,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-тег.
+9 -1
View File
@@ -1,4 +1,12 @@
module.exports = { module.exports = {
extends: ["@commitlint/config-conventional"], extends: ["@commitlint/config-conventional"],
rules: { "type-enum": [2, "always", ["feat", "fix", "perf", "docs", "spec", "refactor", "build", "ci", "test", "style", "chore", "revert"]] }, defaultIgnores: false,
ignores: [
message => /^chore\(release\): \d+\.\d+\.\d+$/.test(message.trim()),
message => /^Merge pull request ['"].+['"] \(#\d+\) from .+ into .+$/.test(message.trim()),
],
rules: {
"type-enum": [2, "always", ["feat", "fix", "perf", "docs", "spec", "refactor", "build", "ci", "test", "style", "chore", "revert"]],
"references-empty": [2, "never"],
},
}; };
+53
View File
@@ -0,0 +1,53 @@
# Requires Compose >= 2.24.4 for !override. Always use a separate project name.
name: wealthledger-dev
x-development-backend: &development-backend
image: wealthledger-backend:development
pull_policy: build
build:
context: ./backend
target: development
read_only: false
environment:
NODE_ENV: development
volumes: !override
- development-data:/data
services:
migration:
<<: *development-backend
backend:
<<: *development-backend
healthcheck:
test: [CMD, node, -e, 'fetch("http://127.0.0.1:3000/api/v1/auth/me").then(async r=>{await r.arrayBuffer();process.exit(r.status===401?0:1)}).catch(()=>process.exit(1))']
interval: 10s
timeout: 3s
start_period: 20s
retries: 6
volumes: !override
- development-data:/data
- ./backend/src:/app/src:ro
frontend:
image: wealthledger-frontend:development
pull_policy: build
build:
context: ./frontend
target: development
read_only: false
volumes:
- ./frontend/src:/app/src:ro
- ./frontend/public:/app/public:ro
environment:
VITE_API_BASE_URL: ''
API_PROXY_TARGET: http://backend:3000
healthcheck:
test: [CMD, node, -e, 'fetch("http://127.0.0.1:8080/").then(async r=>{await r.arrayBuffer();process.exit(r.ok?0:1)}).catch(()=>process.exit(1))']
interval: 10s
timeout: 3s
start_period: 10s
retries: 6
volumes:
development-data:
+51
View File
@@ -0,0 +1,51 @@
name: wealthledger
x-backend: &backend
image: ${BACKEND_IMAGE:?Set versioned backend image}
environment:
NODE_ENV: production
PORT: '3000'
DATABASE_URL: file:/data/wealthledger.db
FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:?Set browser-visible origin}
JWT_SECRET: ${JWT_SECRET:?Set persistent JWT secret}
TOKEN_ENCRYPTION_KEY: ${TOKEN_ENCRYPTION_KEY:?Set persistent encryption key}
LOG_LEVEL: ${LOG_LEVEL:-info}
volumes:
- type: bind
source: ${DATA_DIRECTORY:?Set persistent absolute host data directory}
target: /data
bind:
create_host_path: false
read_only: true
init: true
security_opt: [no-new-privileges:true]
cap_drop: [ALL]
services:
migration:
<<: *backend
command: [node, node_modules/prisma/build/index.js, migrate, deploy]
restart: 'no'
healthcheck:
disable: true
backend:
<<: *backend
scale: 1
restart: unless-stopped
depends_on:
migration:
condition: service_completed_successfully
frontend:
image: ${FRONTEND_IMAGE:?Set versioned frontend image}
ports:
- '127.0.0.1:${FRONTEND_PORT:-8080}:8080'
read_only: true
tmpfs: [/tmp]
security_opt: [no-new-privileges:true]
cap_drop: [ALL]
restart: unless-stopped
depends_on:
backend:
condition: service_healthy
+14 -14
View File
@@ -1,12 +1,12 @@
{ {
"name": "wealth-ledger-workspace", "name": "wealth-ledger-workspace",
"version": "0.1.0", "version": "0.2.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wealth-ledger-workspace", "name": "wealth-ledger-workspace",
"version": "0.1.0", "version": "0.2.2",
"devDependencies": { "devDependencies": {
"@commitlint/cli": "21.2.2", "@commitlint/cli": "21.2.2",
"@commitlint/config-conventional": "21.2.2", "@commitlint/config-conventional": "21.2.2",
@@ -1446,9 +1446,9 @@
} }
}, },
"node_modules/basic-ftp": { "node_modules/basic-ftp": {
"version": "5.3.1", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-6.2.2.tgz",
"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", "integrity": "sha512-LIkJcf/F6bTTfvE3k2YcTxrJsLKKVXiEpHZYkQP6JoqTb7khng7c13ckRTDQbqRh5IkehVH3F00sSlAa2F6DwA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -2124,9 +2124,9 @@
} }
}, },
"node_modules/fast-uri": { "node_modules/fast-uri": {
"version": "3.1.7", "version": "3.1.8",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -2355,9 +2355,9 @@
} }
}, },
"node_modules/ip-address": { "node_modules/ip-address": {
"version": "10.7.0", "version": "10.7.3",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz",
"integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -3439,9 +3439,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "7.29.0", "version": "7.29.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz",
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
+7 -2
View File
@@ -1,12 +1,11 @@
{ {
"name": "wealth-ledger-workspace", "name": "wealth-ledger-workspace",
"version": "0.1.0", "version": "0.2.2",
"private": true, "private": true,
"engines": { "engines": {
"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"
@@ -17,5 +16,11 @@
"@release-it/conventional-changelog": "12.0.0", "@release-it/conventional-changelog": "12.0.0",
"conventional-changelog-conventionalcommits": "10.4.0", "conventional-changelog-conventionalcommits": "10.4.0",
"release-it": "21.0.2" "release-it": "21.0.2"
},
"overrides": {
"basic-ftp": "6.2.2",
"fast-uri": "3.1.8",
"ip-address": "10.7.3",
"undici": "7.29.1"
} }
} }
-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")