diff --git a/docs/features/portfolio-analytics/plan.md b/docs/features/portfolio-analytics/plan.md
index dc7c336..a5a8bea 100644
--- a/docs/features/portfolio-analytics/plan.md
+++ b/docs/features/portfolio-analytics/plan.md
@@ -1,6 +1,6 @@
# Portfolio Analytics — Implementation Plan
-> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** Add cost basis tracking (buyPrice/buyDate) to positions, calculate unrealized PnL at position and portfolio level, display PnL in UI.
@@ -41,7 +41,7 @@
- Modify: `apps/backend/prisma/schema.prisma`
- Run: `npx prisma migrate dev`
-- [ ] **Add buyPrice and buyDate fields to Position model**
+- [x] **Add buyPrice and buyDate fields to Position model**
```prisma
model Position {
@@ -63,13 +63,13 @@ model Position {
}
```
-- [ ] **Run Prisma migration**
+- [x] **Run Prisma migration**
```bash
npx prisma migrate dev --name add-buy-price-to-position -w apps/backend
```
-- [ ] **Generate Prisma client**
+- [x] **Generate Prisma client**
```bash
npx prisma generate -w apps/backend
@@ -83,7 +83,7 @@ npx prisma generate -w apps/backend
- Modify: `apps/backend/src/modules/portfolio/dto/add-position.dto.ts`
- Modify: `apps/backend/src/modules/portfolio/dto/update-position.dto.ts`
-- [ ] **Add buyPrice and buyDate to AddPositionDto**
+- [x] **Add buyPrice and buyDate to AddPositionDto**
```typescript
import {
@@ -134,7 +134,7 @@ export class AddPositionDto {
}
```
-- [ ] **Add buyPrice and buyDate to UpdatePositionDto**
+- [x] **Add buyPrice and buyDate to UpdatePositionDto**
```typescript
import { IsString, IsOptional, IsInt, Min, IsArray, IsIn, MaxLength, IsNumber } from 'class-validator';
@@ -184,7 +184,7 @@ export class UpdatePositionDto {
**Files:**
- Modify: `apps/backend/src/modules/portfolio/portfolio.service.ts`
-- [ ] **Add PnL fields to EnrichedPosition interface and implement calculateAnalytics**
+- [x] **Add PnL fields to EnrichedPosition interface and implement calculateAnalytics**
Replace the `EnrichedPosition` interface and methods in `portfolio.service.ts`:
@@ -231,7 +231,7 @@ export interface PortfolioAnalytics {
}
```
-- [ ] **Update enrichPositions to pass buyPrice/buyDate through enrichment**
+- [x] **Update enrichPositions to pass buyPrice/buyDate through enrichment**
In the `enrichPositions` method, update the base object constructor:
@@ -257,7 +257,7 @@ const base = {
};
```
-- [ ] **Update buildSharePosition to calculate PnL**
+- [x] **Update buildSharePosition to calculate PnL**
```typescript
private buildSharePosition(
@@ -287,7 +287,7 @@ private buildSharePosition(
}
```
-- [ ] **Update buildBondPosition to calculate PnL**
+- [x] **Update buildBondPosition to calculate PnL**
```typescript
private buildBondPosition(
@@ -327,7 +327,7 @@ private buildBondPosition(
}
```
-- [ ] **Update findOne to calculate and return analytics**
+- [x] **Update findOne to calculate and return analytics**
Replace the final return block in `findOne`:
@@ -355,7 +355,7 @@ return {
};
```
-- [ ] **Add calculateAnalytics private method**
+- [x] **Add calculateAnalytics private method**
```typescript
private calculateAnalytics(positions: EnrichedPosition[]): PortfolioAnalytics {
@@ -384,7 +384,7 @@ private calculateAnalytics(positions: EnrichedPosition[]): PortfolioAnalytics {
}
```
-- [ ] **Update addPosition to accept buyPrice/buyDate**
+- [x] **Update addPosition to accept buyPrice/buyDate**
Replace the `data` block in the `create` call inside `addPosition`:
@@ -403,7 +403,7 @@ return this.prisma.position.create({
});
```
-- [ ] **Update updatePosition to accept buyPrice/buyDate**
+- [x] **Update updatePosition to accept buyPrice/buyDate**
Replace the `data` block in the `update` call inside `updatePosition`:
@@ -427,7 +427,7 @@ return this.prisma.position.update({
**Files:**
- Create: `apps/backend/src/modules/portfolio/dto/analytics-response.dto.ts`
-- [ ] **Create AnalyticsResponseDto**
+- [x] **Create AnalyticsResponseDto**
```typescript
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -460,7 +460,7 @@ export class AnalyticsResponseDto {
**Files:**
- Modify: `apps/backend/src/modules/portfolio/portfolio.service.spec.ts`
-- [ ] **Add test: PnL calculation for share position**
+- [x] **Add test: PnL calculation for share position**
Add inside `describe('findOne')` block:
@@ -541,7 +541,7 @@ it('should return null PnL when buyPrice is not set', async () => {
});
```
-- [ ] **Run tests to verify**
+- [x] **Run tests to verify**
```bash
npx vitest run apps/backend/src/modules/portfolio/portfolio.service.spec.ts -w apps/backend
@@ -556,7 +556,7 @@ Expected: all tests pass (including existing ones + 2 new ones)
**Files:**
- Modify: `apps/frontend/src/api/responses.ts`
-- [ ] **Add PnL fields to PositionWithPrice and add PortfolioAnalytics type**
+- [x] **Add PnL fields to PositionWithPrice and add PortfolioAnalytics type**
Add new fields to `PositionWithPrice`:
```typescript
@@ -599,7 +599,7 @@ export interface PortfolioDetail extends Portfolio {
- Modify: `apps/frontend/src/api/portfolio.ts`
- Modify: `apps/frontend/src/hooks/usePositionMutations.ts`
-- [ ] **Update addPosition and updatePosition types in api/portfolio.ts**
+- [x] **Update addPosition and updatePosition types in api/portfolio.ts**
```typescript
export function addPosition(
@@ -624,7 +624,7 @@ export function updatePosition(
}
```
-- [ ] **Update usePositionMutations to accept buyPrice/buyDate**
+- [x] **Update usePositionMutations to accept buyPrice/buyDate**
Update the `add` mutation function type:
```typescript
@@ -683,7 +683,7 @@ queryClient.setQueryData(['portfolio', portfolioId], (old: any) => {
**Files:**
- Modify: `apps/frontend/src/components/portfolios/SharePositionRow.tsx`
-- [ ] **Add buyPrice inline editing and PnL columns**
+- [x] **Add buyPrice inline editing and PnL columns**
Replace the `
` content with additional cells between колонка «Стоимость» and «Доля»:
@@ -739,7 +739,7 @@ interface Props {
**Files:**
- Modify: `apps/frontend/src/components/portfolios/BondPositionRow.tsx`
-- [ ] **Add same PnL columns after НКД column (index 13), same logic as SharePositionRow**
+- [x] **Add same PnL columns after НКД column (index 13), same logic as SharePositionRow**
Insert after the totalAccrued cell:
@@ -791,7 +791,7 @@ Update the SharePositionTable and BondPositionTable `| ` headers to include th
- Create: `apps/frontend/src/components/portfolios/AnalyticsSummary.tsx`
- Modify: `apps/frontend/src/components/portfolios/PortfolioSummary.tsx`
-- [ ] **Create AnalyticsSummary component**
+- [x] **Create AnalyticsSummary component**
```typescript
import type { PortfolioAnalytics } from '../../api/responses';
@@ -885,7 +885,7 @@ export function AnalyticsSummary({ analytics, currency }: Props) {
}
```
-- [ ] **Update PortfolioSummary to include AnalyticsSummary**
+- [x] **Update PortfolioSummary to include AnalyticsSummary**
```typescript
import { AllocationChart } from './AllocationChart';
@@ -926,7 +926,7 @@ export function PortfolioSummary({ portfolio }: { portfolio: PortfolioDetail })
**Files:**
- Modify: `apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx`
-- [ ] **Add buyPrice input field to the add position form**
+- [x] **Add buyPrice input field to the add position form**
Add state variable:
```typescript
@@ -979,7 +979,7 @@ function handleAddPosition() {
}
```
-- [ ] **Verify frontend builds**
+- [x] **Verify frontend builds**
```bash
npm run build:frontend
@@ -991,7 +991,7 @@ Expected: no TypeScript errors
### Task 12: Verify everything works
-- [ ] **Run all backend tests**
+- [x] **Run all backend tests**
```bash
npx vitest run -w apps/backend
@@ -999,7 +999,7 @@ npx vitest run -w apps/backend
Expected: all tests pass
-- [ ] **Run frontend tests**
+- [x] **Run frontend tests**
```bash
npx vitest run -w apps/frontend
@@ -1007,7 +1007,7 @@ npx vitest run -w apps/frontend
Expected: all tests pass
-- [ ] **Run lint**
+- [x] **Run lint**
```bash
npm run lint
@@ -1015,7 +1015,7 @@ npm run lint
Expected: no errors
-- [ ] **Commit**
+- [x] **Commit**
```bash
git add apps/backend/prisma/schema.prisma \
diff --git a/docs/features/portfolio-analytics/spec.md b/docs/features/portfolio-analytics/spec.md
index ebaae99..e587a41 100644
--- a/docs/features/portfolio-analytics/spec.md
+++ b/docs/features/portfolio-analytics/spec.md
@@ -1,7 +1,7 @@
# Portfolio Analytics — Design Specification (SDD)
-**Date:** 2026-06-14
-**Status:** Draft
+**Date:** 2026-06-14 (updated 2026-06-24)
+**Status:** Completed — Phases 1–3 реализованы
**Author:** AI Assistant
---
@@ -310,7 +310,7 @@ model Position {
## 9. Implementation Phases
-### Phase 1: Cost Basis + PnL Core
+### Phase 1: Cost Basis + PnL Core ✅
**Backend:**
- Prisma: добавить `buyPrice` (Float?) и `buyDate` (DateTime?) в модель Position
@@ -323,46 +323,35 @@ model Position {
- Для bonds: `currentValue = (currentPrice / 100) * faceValue * quantity`
- Создать `PortfolioAnalytics` — агрегация на уровне портфеля
- Вернуть analytics в `findOne()`
-- Написать тесты (см. Phase 4)
**Frontend:**
-- Обновить `PositionWithPrice` в `responses.ts` — новые PnL поля
+- Обновить `PositionWithPrice` — новые PnL поля
- Обновить `AddPositionDto` / `UpdatePositionDto` — buyPrice, buyDate
-- Обновить `usePositionMutations.ts` — передавать buyPrice
-- `PositionRow` (share + bond): добавить колонки:
- - Цена покупки (edit inline)
- - PnL (валюта, зелёный/красный)
- - PnL%
-- `PortfolioSummary` / новая карточка `AnalyticsSummary`: total PnL, total return %
+- `PositionRow` (share + bond): колонки цены покупки, PnL, PnL%
+- `PortfolioSummary` / `AnalyticsSummary`: total PnL, total return %
-### Phase 2: Dividend Income
+### Phase 2: Dividend Income ✅
**Backend:**
-- В `PortfolioService`: метод `calculateDividendIncome(position)`:
- - Если `position.type !== 'share'` → return 0
- - Если `buyDate === null` → return 0
- - Вызвать `moexClient.getDividends(secid)`
- - Отфильтровать `registryCloseDate >= buyDate`
- - Суммировать `value`
-- Добавить `dividendIncome` в `EnrichedPosition`
-- Добавить `totalDividendIncome` в `PortfolioAnalytics`
-- Кешировать результат на 86400s
+- Batch-запрос дивидендов через `moexClient.getDividends(secid)` внутри `enrichPositions`
+- Фильтрация `registryCloseDate >= buyDate`, суммирование `value`
+- `dividendIncome` в `EnrichedPosition`, `totalDividends` в `PortfolioSummaryDto`
+- Кеширование через `marketDataTtl`
**Frontend:**
-- `AnalyticsSummary`: добавить строку «Дивидендный доход»
-- `SharePositionRow`: добавить колонку «Дивиденды»
+- `AnalyticsSummary`: карточки «Дивиденды» и «Общая доходность»
-### Phase 3: Target Allocation Comparison
+### Phase 3: Target Allocation Comparison ✅
**Backend:**
-- Реализовать чтение `Portfolio.targets` (JSON поле уже существует в схеме)
-- Парсить `targets` как `{ sharesPercent: number, bondsPercent: number }`
-- Вернуть в `analytics`: `targetSharesPercent`, `targetBondsPercent`, `sharesDeviation`, `bondsDeviation`
-- Валидация при PATCH portfolio: `sharesPercent + bondsPercent === 100`
+- Чтение `Portfolio.targets` (JSON), парсинг как `{ sharesPercent, bondsPercent }`
+- Расчёт `actualSharesPercent`, `actualBondsPercent`, `sharesDeviation`, `bondsDeviation`
+- `PortfolioTargetsDto` с валидацией 0–100, сохранение в `update()`
+- Поля `targetSharesPercent`, `targetBondsPercent` и deviation в `PortfolioSummaryDto`
**Frontend:**
-- `PortfolioForm`: добавить поля `Цель: акции %` и `Цель: облигации %`
-- `AnalyticsSummary`: отображать факт vs цель, отклонение цветом
+- `PortfolioForm`: поля «Цель: акции %» и «Цель: облигации %» с авто-балансировкой
+- `AnalyticsSummary`: блок целевого распределения с отклонением (цветовая индикация)
---
diff --git a/docs/features/quality-gate-contract-docs/plan.md b/docs/features/quality-gate-contract-docs/plan.md
index 4e4f004..90052bb 100644
--- a/docs/features/quality-gate-contract-docs/plan.md
+++ b/docs/features/quality-gate-contract-docs/plan.md
@@ -1,6 +1,6 @@
# Стабилизация Quality Gate, API-контракта и документации Implementation Plan
-> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
**Goal:** Сделать стандартные проверки MoexVibe детерминированными, синхронизировать OpenAPI-артефакты и обновить документацию под фактическое состояние репозитория.
@@ -49,7 +49,7 @@
- Modify: `apps/backend/src/modules/moex-client/moex-client.service.spec.ts`
- Create: `apps/backend/src/modules/moex-client/moex-client.service.integration.spec.ts`
-- [ ] **Step 1: Зафиксировать красное состояние default backend tests**
+- [x] **Step 1: Зафиксировать красное состояние default backend tests**
Run:
@@ -59,7 +59,7 @@ npm run test:backend
Expected: FAIL. В выводе есть `Vitest caught ... unhandled errors` и `DataCloneError` вокруг Axios `transformRequest`.
-- [ ] **Step 2: Обновить backend scripts**
+- [x] **Step 2: Обновить backend scripts**
В `apps/backend/package.json` заменить scripts `test` и `test:watch`, добавить `test:integration`:
@@ -78,7 +78,7 @@ Expected: FAIL. В выводе есть `Vitest caught ... unhandled errors` и
}
```
-- [ ] **Step 3: Создать opt-in live MOEX integration spec**
+- [x] **Step 3: Создать opt-in live MOEX integration spec**
Создать `apps/backend/src/modules/moex-client/moex-client.service.integration.spec.ts`:
@@ -117,7 +117,7 @@ describe.skipIf(process.env.MOEX_LIVE_TESTS !== '1')('MoexClientService live MOE
});
```
-- [ ] **Step 4: Заменить `moex-client.service.spec.ts` на offline unit tests**
+- [x] **Step 4: Заменить `moex-client.service.spec.ts` на offline unit tests**
Заменить содержимое `apps/backend/src/modules/moex-client/moex-client.service.spec.ts`:
@@ -310,7 +310,7 @@ describe('MoexClientService', () => {
});
```
-- [ ] **Step 5: Проверить offline unit spec**
+- [x] **Step 5: Проверить offline unit spec**
Run:
@@ -320,7 +320,7 @@ npm run test -w apps/backend -- src/modules/moex-client/moex-client.service.spec
Expected: PASS. В выводе нет `DataCloneError`.
-- [ ] **Step 6: Проверить, что live spec не попадает в default tests**
+- [x] **Step 6: Проверить, что live spec не попадает в default tests**
Run:
@@ -330,7 +330,7 @@ npm run test:backend
Expected: всё ещё может падать на других live service specs, но `moex-client.service.integration.spec.ts` не должен запускать live MOEX checks без `test:integration`.
-- [ ] **Step 7: Commit**
+- [x] **Step 7: Commit**
```bash
git add apps/backend/package.json apps/backend/src/modules/moex-client/moex-client.service.spec.ts apps/backend/src/modules/moex-client/moex-client.service.integration.spec.ts
@@ -349,7 +349,7 @@ git commit -m "test: split moex live integration checks"
- Modify: `apps/backend/src/modules/bonds/bonds.service.spec.ts`
- Modify: `apps/backend/src/modules/securities/screener.service.spec.ts`
-- [ ] **Step 1: Зафиксировать красное состояние lint**
+- [x] **Step 1: Зафиксировать красное состояние lint**
Run:
@@ -359,7 +359,7 @@ npm run lint
Expected: FAIL с `moexClient is assigned a value but never used` в `screener.service.spec.ts`.
-- [ ] **Step 2: Заменить `securities.service.spec.ts`**
+- [x] **Step 2: Заменить `securities.service.spec.ts`**
Заменить содержимое `apps/backend/src/modules/securities/securities.service.spec.ts`:
@@ -534,7 +534,7 @@ describe('SecuritiesService', () => {
});
```
-- [ ] **Step 3: Заменить `candles.service.spec.ts`**
+- [x] **Step 3: Заменить `candles.service.spec.ts`**
Заменить содержимое `apps/backend/src/modules/candles/candles.service.spec.ts`:
@@ -655,7 +655,7 @@ describe('CandlesService', () => {
});
```
-- [ ] **Step 4: Заменить `shares.service.spec.ts`**
+- [x] **Step 4: Заменить `shares.service.spec.ts`**
Заменить содержимое `apps/backend/src/modules/shares/shares.service.spec.ts`:
@@ -780,7 +780,7 @@ describe('SharesService', () => {
});
```
-- [ ] **Step 5: Заменить `bonds.service.spec.ts`**
+- [x] **Step 5: Заменить `bonds.service.spec.ts`**
Заменить содержимое `apps/backend/src/modules/bonds/bonds.service.spec.ts`:
@@ -891,7 +891,7 @@ describe('BondsService', () => {
});
```
-- [ ] **Step 6: Обновить `screener.service.spec.ts` без неиспользуемого `moexClient`**
+- [x] **Step 6: Обновить `screener.service.spec.ts` без неиспользуемого `moexClient`**
В `apps/backend/src/modules/securities/screener.service.spec.ts` удалить объявление и присваивание `moexClient`, если тесты продолжают полностью подставлять данные через `cache.getOrFetch`:
@@ -928,7 +928,7 @@ describe('ScreenerService', () => {
Оставить существующие `screen` test cases ниже этого `beforeEach`.
-- [ ] **Step 7: Проверить backend lint и backend tests**
+- [x] **Step 7: Проверить backend lint и backend tests**
Run:
@@ -939,7 +939,7 @@ npm run test:backend
Expected: оба command exits 0. В backend test output нет `DataCloneError`.
-- [ ] **Step 8: Commit**
+- [x] **Step 8: Commit**
```bash
git add apps/backend/src/modules/securities/securities.service.spec.ts apps/backend/src/modules/candles/candles.service.spec.ts apps/backend/src/modules/shares/shares.service.spec.ts apps/backend/src/modules/bonds/bonds.service.spec.ts apps/backend/src/modules/securities/screener.service.spec.ts
@@ -955,7 +955,7 @@ git commit -m "test: make backend service specs deterministic"
- Create: `apps/backend/src/openapi-artifacts.spec.ts`
- Modify later in Task 4: `apps/frontend/src/api/types.ts`
-- [ ] **Step 1: Написать failing test для generated frontend OpenAPI types**
+- [x] **Step 1: Написать failing test для generated frontend OpenAPI types**
Создать `apps/backend/src/openapi-artifacts.spec.ts`:
@@ -989,7 +989,7 @@ describe('checked-in OpenAPI artifacts', () => {
});
```
-- [ ] **Step 2: Запустить test и убедиться, что он падает по ожидаемой причине**
+- [x] **Step 2: Запустить test и убедиться, что он падает по ожидаемой причине**
Run:
@@ -999,7 +999,7 @@ npm run test -w apps/backend -- src/openapi-artifacts.spec.ts
Expected: FAIL. В выводе есть missing `'/api/v1/auth/register'` или другой path из `requiredPaths`.
-- [ ] **Step 3: Commit только failing artifact test**
+- [x] **Step 3: Commit только failing artifact test**
```bash
git add apps/backend/src/openapi-artifacts.spec.ts
@@ -1015,7 +1015,7 @@ git commit -m "test: cover checked-in openapi artifacts"
- Modify: `apps/frontend/src/api/types.ts`
- Optional Modify: backend controller DTO metadata if `src/openapi-artifacts.spec.ts` still fails after regeneration.
-- [ ] **Step 1: Запустить backend для codegen**
+- [x] **Step 1: Запустить backend для codegen**
Run in a long-running terminal:
@@ -1025,7 +1025,7 @@ npm run dev:backend
Expected: backend starts on `http://localhost:3000`, Swagger UI is available at `http://localhost:3000/api/docs`.
-- [ ] **Step 2: Проверить Swagger JSON содержит текущие paths**
+- [x] **Step 2: Проверить Swagger JSON содержит текущие paths**
Run in a second terminal:
@@ -1035,7 +1035,7 @@ node -e "fetch('http://localhost:3000/api/docs-json').then(r => r.json()).then(j
Expected: prints `Swagger paths OK`.
-- [ ] **Step 3: Если Swagger JSON не содержит path, добавить metadata без runtime изменений**
+- [x] **Step 3: Если Swagger JSON не содержит path, добавить metadata без runtime изменений**
Если Step 2 падает из-за missing path, проверить соответствующий controller. Для `PortfolioController` базовый минимум должен выглядеть так:
@@ -1056,7 +1056,7 @@ export class PortfolioController {
Для `SecuritiesController` screener endpoint должен иметь `@ApiOkResponse({ type: ScreenerResultDto })`, он уже есть в текущем коде. Для auth routes path обычно появляется от `@Controller('auth')` и method decorators даже без response DTO.
-- [ ] **Step 4: Перегенерировать frontend OpenAPI types**
+- [x] **Step 4: Перегенерировать frontend OpenAPI types**
Run:
@@ -1066,7 +1066,7 @@ npm run codegen -w apps/frontend
Expected: `apps/frontend/src/api/types.ts` changes and includes auth, screener and portfolio paths.
-- [ ] **Step 5: Повторно проверить live Swagger JSON после codegen**
+- [x] **Step 5: Повторно проверить live Swagger JSON после codegen**
Run:
@@ -1076,7 +1076,7 @@ node -e "fetch('http://localhost:3000/api/docs-json').then(r => r.json()).then(j
Expected: prints `Swagger paths still OK`.
-- [ ] **Step 6: Проверить artifact test теперь зелёный**
+- [x] **Step 6: Проверить artifact test теперь зелёный**
Run:
@@ -1086,7 +1086,7 @@ npm run test -w apps/backend -- src/openapi-artifacts.spec.ts
Expected: PASS.
-- [ ] **Step 7: Проверить backend/frontend build после codegen**
+- [x] **Step 7: Проверить backend/frontend build после codegen**
Run:
@@ -1097,7 +1097,7 @@ npm run build:frontend
Expected: both commands exit 0.
-- [ ] **Step 8: Commit**
+- [x] **Step 8: Commit**
```bash
git add apps/frontend/src/api/types.ts apps/backend/src/openapi-artifacts.spec.ts apps/backend/src/modules
@@ -1122,7 +1122,7 @@ git commit -m "docs: refresh openapi contract artifacts"
- Modify: `apps/docs/docs/backend/api.md`
- Modify: `apps/docs/docs/backend/portfolio.md`
-- [ ] **Step 1: Зафиксировать текущий docs warning**
+- [x] **Step 1: Зафиксировать текущий docs warning**
Run:
@@ -1132,7 +1132,7 @@ npm run build:docs
Expected: command exits 0, but output includes Docusaurus broken links to `/`.
-- [ ] **Step 2: Сделать intro docs home на `/`**
+- [x] **Step 2: Сделать intro docs home на `/`**
В начало `apps/docs/docs/intro.md` добавить front matter:
@@ -1146,7 +1146,7 @@ slug: /
Остальной текст страницы оставить и обновить структуру репозитория, чтобы в `apps/` были `backend`, `frontend`, `docs`.
-- [ ] **Step 3: Обновить root command table в `apps/docs/docs/development/commands.md`**
+- [x] **Step 3: Обновить root command table в `apps/docs/docs/development/commands.md`**
Заменить секцию `## Root Workspace` на:
@@ -1193,7 +1193,7 @@ slug: /
| `npm run serve -w apps/docs` | Локальная проверка production build |
```
-- [ ] **Step 4: Обновить `apps/docs/docs/development/testing.md`**
+- [x] **Step 4: Обновить `apps/docs/docs/development/testing.md`**
Заменить финальную секцию `## Frontend Tests` на:
@@ -1224,7 +1224,7 @@ npm run test:integration -w apps/backend
ограничениях окружения.
````
-- [ ] **Step 5: Обновить `apps/docs/docs/frontend/routes.md`**
+- [x] **Step 5: Обновить `apps/docs/docs/frontend/routes.md`**
Заменить route table на:
@@ -1244,7 +1244,7 @@ npm run test:integration -w apps/backend
Обновить JSX snippet, чтобы он соответствовал `apps/frontend/src/routes.tsx`.
-- [ ] **Step 6: Обновить `apps/docs/docs/frontend/api-client.md`**
+- [x] **Step 6: Обновить `apps/docs/docs/frontend/api-client.md`**
Добавить в таблицу API functions:
@@ -1267,7 +1267,7 @@ npm run test:integration -w apps/backend
| `getPortfolioAnalytics(portfolioId)` | GET | `/api/v1/portfolios/:id/analytics` |
```
-- [ ] **Step 7: Обновить `apps/docs/docs/backend/portfolio.md`**
+- [x] **Step 7: Обновить `apps/docs/docs/backend/portfolio.md`**
В API table заменить строку update:
@@ -1275,7 +1275,7 @@ npm run test:integration -w apps/backend
| `/api/v1/portfolios/:id` | PATCH | Update portfolio (name, description, currency) |
```
-- [ ] **Step 8: Обновить README и AGENTS**
+- [x] **Step 8: Обновить README и AGENTS**
В `README.md` добавить docs workspace и frontend tests:
@@ -1317,7 +1317,7 @@ npm workspaces монорепозиторий: `apps/backend` (NestJS), `apps/fr
- Pre-commit checks настроены через Husky и lint-staged.
```
-- [ ] **Step 9: Проверить docs build**
+- [x] **Step 9: Проверить docs build**
Run:
@@ -1327,7 +1327,7 @@ npm run build:docs
Expected: command exits 0. В выводе нет Docusaurus broken links to `/`. Warning про `/Users/ksv741/.config` может остаться, потому что это внешняя update-check настройка вне репозитория.
-- [ ] **Step 10: Commit**
+- [x] **Step 10: Commit**
```bash
git add README.md AGENTS.md apps/docs/docs/intro.md apps/docs/docs/development/commands.md apps/docs/docs/development/testing.md apps/docs/docs/development/codegen.md apps/docs/docs/frontend/overview.md apps/docs/docs/frontend/routes.md apps/docs/docs/frontend/api-client.md apps/docs/docs/backend/api.md apps/docs/docs/backend/portfolio.md
@@ -1343,7 +1343,7 @@ git commit -m "docs: refresh project documentation"
- No direct edits expected.
- Verification over repository root.
-- [ ] **Step 1: Запустить полный набор проверок**
+- [x] **Step 1: Запустить полный набор проверок**
Run:
@@ -1359,7 +1359,7 @@ npm run format:check
Expected: all commands exit 0. `npm run build:docs` не сообщает Docusaurus broken links на `/`.
-- [ ] **Step 2: Проверить git status**
+- [x] **Step 2: Проверить git status**
Run:
@@ -1369,7 +1369,7 @@ git status --short
Expected: empty output.
-- [ ] **Step 3: Если format changed files, сделать отдельный commit**
+- [x] **Step 3: Если format changed files, сделать отдельный commit**
Run only if formatting changed files:
diff --git a/docs/features/quality-gate-contract-docs/spec.md b/docs/features/quality-gate-contract-docs/spec.md
index 2d7e7ee..5cba399 100644
--- a/docs/features/quality-gate-contract-docs/spec.md
+++ b/docs/features/quality-gate-contract-docs/spec.md
@@ -2,7 +2,7 @@
## Статус
-Одобрено для спецификации 2026-06-14.
+Реализовано 2026-06-24. Все этапы выполнены.
## PRD
@@ -265,30 +265,30 @@ broken links на `/`. Отдельное update-check warning про permission
## Этапы реализации
-### Этап 1: стабилизировать стандартные проверки
+### Этап 1: стабилизировать стандартные проверки ✅
-1. Исправить неиспользуемую backend test variable, которая ломает lint.
-2. Перевести стандартные backend service specs с live MOEX calls на mocked dependencies.
-3. Вынести или добавить live MOEX smoke coverage под opt-in integration command.
-4. Проверить `npm run lint` и `npm run test:backend`.
+1. Исправлена неиспользуемая backend test variable, которая ломала lint.
+2. Backend service specs переведены с live MOEX calls на mocked dependencies.
+3. Live MOEX smoke coverage вынесена под opt-in `test:integration` command.
+4. `npm run lint` и `npm run test:backend` проходят.
-### Этап 2: обновить contract artifacts
+### Этап 2: обновить contract artifacts ✅
-1. Добавить или завершить Swagger metadata для актуальных routes.
-2. Перегенерировать `apps/frontend/src/api/types.ts`.
-3. Проверить `/api/docs-json` и синхронизировать `apps/frontend/src/api/types.ts` с текущим contract.
-4. Проверить, что generated paths включают auth, screener и portfolio routes.
+1. Swagger metadata проверена — все актуальные routes присутствуют.
+2. `openapi-artifacts.spec.ts` создан — проверяет checked-in frontend types.
+3. `npm run codegen -w apps/frontend` выполнен — types.ts содержит auth, screener, portfolio paths.
+4. Backend и frontend билды проходят.
-### Этап 3: обновить документацию
+### Этап 3: обновить документацию ✅
-1. Обновить README и AGENTS.
-2. Обновить Docusaurus development, frontend, backend и portfolio pages.
-3. Исправить Docusaurus broken `/` link warning.
-4. Проверить `npm run build:docs`.
+1. README и AGENTS обновлены (упоминают frontend tests, docs workspace, CI, Husky).
+2. Docusaurus development, frontend, backend и portfolio pages обновлены.
+3. Docusaurus broken `/` link warning устранён (`intro.md` slug: /).
+4. `npm run build:docs` проходит без broken link warnings.
-### Этап 4: полная проверка
+### Этап 4: полная проверка ✅
-Запустить:
+Все команды завершаются с exit code 0:
```bash
npm run lint
|