mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 07:30:16 +03:00
feat: Добавить VitePress
This commit is contained in:
75
.github/workflows/docs.yml
vendored
Normal file
75
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
name: Documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/docs.yml'
|
||||||
|
- 'docs/**'
|
||||||
|
- 'site/**'
|
||||||
|
- 'scripts/check-docs.mjs'
|
||||||
|
- 'package.json'
|
||||||
|
- 'package-lock.json'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/docs.yml'
|
||||||
|
- 'docs/**'
|
||||||
|
- 'site/**'
|
||||||
|
- 'scripts/check-docs.mjs'
|
||||||
|
- 'package.json'
|
||||||
|
- 'package-lock.json'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: npm
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Check specification
|
||||||
|
run: npm run check:docs
|
||||||
|
|
||||||
|
- name: Build documentation
|
||||||
|
run: npm run docs:build
|
||||||
|
|
||||||
|
- name: Check documentation search
|
||||||
|
run: npm run check:docs-search
|
||||||
|
|
||||||
|
- name: Configure Pages
|
||||||
|
uses: actions/configure-pages@v5
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: site/.vitepress/dist
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
site/.vitepress/cache/
|
||||||
|
site/.vitepress/dist/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
- `docs/` — исходная документация и спецификация SLM Design.
|
- `docs/` — новый нормативный корпус и материалы сайта.
|
||||||
|
- `old-docs/` — действующая legacy-документация для текущего skill.
|
||||||
|
- `site/` — VitePress-конфигурация, тема и статические ресурсы.
|
||||||
- `src-skills/` — исходники agent skills.
|
- `src-skills/` — исходники agent skills.
|
||||||
- `skills/` — собранные skills для установки через `npx skills`.
|
- `skills/` — собранные skills для установки через `npx skills`.
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ npm run build
|
|||||||
npm run check
|
npm run check
|
||||||
```
|
```
|
||||||
|
|
||||||
`npm run build` пересобирает `skills/slm-design/` из `docs/` и `src-skills/slm-design/`. Не редактируй собранные файлы вручную.
|
`npm run build` пересобирает текущий `skills/slm-design/` из `old-docs/` и `src-skills/slm-design/`. Не редактируй собранные файлы вручную.
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
|
|||||||
24
docs/en/index.md
Normal file
24
docs/en/index.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
title: SLM Design in English
|
||||||
|
titleTemplate: false
|
||||||
|
sidebar: false
|
||||||
|
hero:
|
||||||
|
name: English edition
|
||||||
|
text: Translation is planned
|
||||||
|
tagline: The Russian specification is currently the only normative source. The English edition will preserve its structure and rule IDs.
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Open Russian specification
|
||||||
|
link: /ru/specification/
|
||||||
|
- theme: alt
|
||||||
|
text: Language selection
|
||||||
|
link: /
|
||||||
|
features:
|
||||||
|
- title: No partial translation
|
||||||
|
details: An incomplete English rule set is not published as normative documentation.
|
||||||
|
- title: Stable identifiers
|
||||||
|
details: Future translated requirements will use the same SLM rule IDs as the Russian source.
|
||||||
|
- title: Equal URL structure
|
||||||
|
details: English documentation is reserved under /en/ alongside the Russian /ru/ section.
|
||||||
|
---
|
||||||
24
docs/index.md
Normal file
24
docs/index.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
title: SLM Design
|
||||||
|
titleTemplate: false
|
||||||
|
sidebar: false
|
||||||
|
hero:
|
||||||
|
name: SLM Design
|
||||||
|
text: Explicit architecture boundaries
|
||||||
|
tagline: A draft specification for ownership, dependencies, runtime, and lifecycle in product applications.
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Русская спецификация
|
||||||
|
link: /ru/
|
||||||
|
- theme: alt
|
||||||
|
text: English
|
||||||
|
link: /en/
|
||||||
|
features:
|
||||||
|
- title: Base SLM
|
||||||
|
details: A complete minimal architecture built around explicit ownership and five application layers.
|
||||||
|
- title: Independent overlays
|
||||||
|
details: Advanced and Pro add separate rule sets directly to base SLM without inheriting each other.
|
||||||
|
- title: Stable rules
|
||||||
|
details: Every normative requirement has a permanent rule ID suitable for reviews and automated checks.
|
||||||
|
---
|
||||||
28
docs/ru/index.md
Normal file
28
docs/ru/index.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
layout: home
|
||||||
|
title: SLM Design
|
||||||
|
titleTemplate: false
|
||||||
|
sidebar: false
|
||||||
|
hero:
|
||||||
|
name: Документация SLM Design
|
||||||
|
text: Один портал для правил и практики
|
||||||
|
tagline: Нормативная спецификация, реестр правил и будущий архитектурный гайд в единой структуре.
|
||||||
|
actions:
|
||||||
|
- theme: brand
|
||||||
|
text: Открыть спецификацию
|
||||||
|
link: /ru/specification/
|
||||||
|
- theme: alt
|
||||||
|
text: Найти правило
|
||||||
|
link: /ru/specification/rules
|
||||||
|
features:
|
||||||
|
- title: SLM Design Specification
|
||||||
|
details: Нормативный источник архитектурных правил, ownership boundaries и требований соответствия.
|
||||||
|
link: /ru/specification/
|
||||||
|
linkText: Читать спецификацию
|
||||||
|
- title: Реестр правил
|
||||||
|
details: Все Base, Advanced и Pro rules с фильтрами, точными anchors и копируемыми permalink-ссылками.
|
||||||
|
link: /ru/specification/rules
|
||||||
|
linkText: Открыть реестр
|
||||||
|
- title: Architecture Guide
|
||||||
|
details: Будущий учебный материал для последовательного изучения и практического применения Specification.
|
||||||
|
---
|
||||||
@@ -17,7 +17,7 @@ src/
|
|||||||
└── shared/
|
└── shared/
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-ARCH-001 - ОБЯЗАН.** Base SLM-приложение должно разделять код по ответственности между слоями `app`, `compositions`, `infra`, `ui` и `shared`.
|
**SLM-BASE-ARCH-001 - ОБЯЗАН.** Base SLM-приложение должно разделять код по ответственности между слоями `app`, `compositions`, `infra`, `ui` и `shared`.
|
||||||
|
|
||||||
Не каждый слой обязан содержать код в минимальном приложении. Пустые папки и speculative scaffolding не требуются.
|
Не каждый слой обязан содержать код в минимальном приложении. Пустые папки и speculative scaffolding не требуются.
|
||||||
|
|
||||||
@@ -42,11 +42,11 @@ shared -/-> остальные SLM-слои
|
|||||||
|
|
||||||
Framework APIs и external packages регулируются ответственностью импортирующего слоя и не показаны как SLM-слои.
|
Framework APIs и external packages регулируются ответственностью импортирующего слоя и не показаны как SLM-слои.
|
||||||
|
|
||||||
**SLM-ARCH-002 - ОБЯЗАН.** Верхнеуровневое направление зависимостей между base SLM-слоями должно соблюдаться для runtime imports и type imports, кроме явно описанных исключений.
|
**SLM-BASE-ARCH-002 - ОБЯЗАН.** Верхнеуровневое направление зависимостей между base SLM-слоями должно соблюдаться для runtime imports и type imports, кроме явно описанных исключений.
|
||||||
|
|
||||||
**SLM-ARCH-003 - ЗАПРЕЩЕНО.** Нижний слой не может импортировать `app` или `compositions`.
|
**SLM-BASE-ARCH-003 - ЗАПРЕЩЕНО.** Нижний слой не может импортировать `app` или `compositions`.
|
||||||
|
|
||||||
**SLM-ARCH-004 - ЗАПРЕЩЕНО.** `infra`, `ui` и `shared` не могут владеть product wiring или выступать service locator для application modules.
|
**SLM-BASE-ARCH-004 - ЗАПРЕЩЕНО.** `infra`, `ui` и `shared` не могут владеть product wiring или выступать service locator для application modules.
|
||||||
|
|
||||||
## Путь данных
|
## Путь данных
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ app
|
|||||||
-> external source
|
-> external source
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-ARCH-005 - ОБЯЗАН.** Каждый переход product data должен сохранять ownership: framework связывает, product owner определяет semantics, а technical capability не присваивает себе product model.
|
**SLM-BASE-ARCH-005 - ОБЯЗАН.** Каждый переход product data должен сохранять ownership: framework связывает, product owner определяет semantics, а technical capability не присваивает себе product model.
|
||||||
|
|
||||||
## Путь UI
|
## Путь UI
|
||||||
|
|
||||||
@@ -25,11 +25,11 @@ SLM + Advanced
|
|||||||
SLM + Pro
|
SLM + Pro
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-MODE-001 - ОБЯЗАН.** Приложение должно зафиксировать использование base SLM и, при наличии, ровно одного overlay: `Advanced` или `Pro`.
|
**SLM-BASE-MODE-001 - ОБЯЗАН.** Приложение должно зафиксировать использование base SLM и, при наличии, ровно одного overlay: `Advanced` или `Pro`.
|
||||||
|
|
||||||
**SLM-MODE-002 - ЗАПРЕЩЕНО.** Одно приложение не может одновременно заявлять соответствие `SLM Advanced` и `SLM Pro`.
|
**SLM-BASE-MODE-002 - ЗАПРЕЩЕНО.** Одно приложение не может одновременно заявлять соответствие `SLM Advanced` и `SLM Pro`.
|
||||||
|
|
||||||
**SLM-MODE-003 - ОБЯЗАН.** Выбранный overlay должен применяться ко всему приложению в пределах одной SLM application boundary.
|
**SLM-BASE-MODE-003 - ОБЯЗАН.** Выбранный overlay должен применяться ко всему приложению в пределах одной SLM application boundary.
|
||||||
|
|
||||||
Выбор выполняет команда на стадии планирования. Сигналами могут быть количество product responsibilities, связанность modules, runtime state, client/server execution, lifecycle risks и количество команд разработки. Фиксированные числовые пороги не устанавливаются.
|
Выбор выполняет команда на стадии планирования. Сигналами могут быть количество product responsibilities, связанность modules, runtime state, client/server execution, lifecycle risks и количество команд разработки. Фиксированные числовые пороги не устанавливаются.
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ SLM + Pro
|
|||||||
Base-правило имеет идентификатор вида:
|
Base-правило имеет идентификатор вида:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
SLM-AREA-NNN
|
SLM-BASE-AREA-NNN
|
||||||
```
|
```
|
||||||
|
|
||||||
Mode-specific правила имеют идентификаторы:
|
Mode-specific правила имеют идентификаторы:
|
||||||
@@ -54,15 +54,15 @@ SLM-ADV-AREA-NNN
|
|||||||
SLM-PRO-AREA-NNN
|
SLM-PRO-AREA-NNN
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-MODE-004 - ОБЯЗАН.** Base-правила SLM применяются при любом выбранном варианте архитектуры. Если overlay явно заменяет base rule только в определённом scope, исходное base-правило продолжает действовать за пределами этого scope.
|
**SLM-BASE-MODE-004 - ОБЯЗАН.** Base-правила SLM применяются при любом выбранном варианте архитектуры. Если overlay явно заменяет base rule только в определённом scope, исходное base-правило продолжает действовать за пределами этого scope.
|
||||||
|
|
||||||
**SLM-MODE-005 - ОБЯЗАН.** Для `SLM Advanced` применяются только base-правила и правила из `modes/advanced`.
|
**SLM-BASE-MODE-005 - ОБЯЗАН.** Для `SLM Advanced` применяются только base-правила и правила из `modes/advanced`.
|
||||||
|
|
||||||
**SLM-MODE-006 - ОБЯЗАН.** Для `SLM Pro` применяются только base-правила и правила из `modes/pro`.
|
**SLM-BASE-MODE-006 - ОБЯЗАН.** Для `SLM Pro` применяются только base-правила и правила из `modes/pro`.
|
||||||
|
|
||||||
**SLM-MODE-007 - ЗАПРЕЩЕНО.** Правило другого overlay не может использоваться как обязательное требование, разрешение или исключение.
|
**SLM-BASE-MODE-007 - ЗАПРЕЩЕНО.** Правило другого overlay не может использоваться как обязательное требование, разрешение или исключение.
|
||||||
|
|
||||||
**SLM-MODE-008 - ОБЯЗАН.** Mode-specific правило, заменяющее base-поведение, должно явно назвать заменяемый base rule ID или нормативный раздел и точный scope замены.
|
**SLM-BASE-MODE-008 - ОБЯЗАН.** Mode-specific правило, заменяющее base-поведение, должно явно назвать заменяемый base rule ID или нормативный раздел и точный scope замены.
|
||||||
|
|
||||||
## Независимые overlays
|
## Независимые overlays
|
||||||
|
|
||||||
@@ -76,6 +76,6 @@ SLM-PRO-AREA-NNN
|
|||||||
|
|
||||||
## Изменение overlay
|
## Изменение overlay
|
||||||
|
|
||||||
**SLM-MODE-009 - МОЖЕТ.** Команда может подключить, заменить или удалить overlay при изменении требований к архитектуре.
|
**SLM-BASE-MODE-009 - МОЖЕТ.** Команда может подключить, заменить или удалить overlay при изменении требований к архитектуре.
|
||||||
|
|
||||||
**SLM-MODE-010 - ОБЯЗАН.** После изменения конфигурации приложение может заявлять соответствие только после выполнения применимых base-правил с учётом scoped replacements и, при наличии, полного rule set выбранного overlay.
|
**SLM-BASE-MODE-010 - ОБЯЗАН.** После изменения конфигурации приложение может заявлять соответствие только после выполнения применимых base-правил с учётом scoped replacements и, при наличии, полного rule set выбранного overlay.
|
||||||
39
docs/ru/specification/foundations.md
Normal file
39
docs/ru/specification/foundations.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
title: Основные инварианты
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Основные Инварианты
|
||||||
|
|
||||||
|
SLM Design организует frontend-приложение по владельцам ответственности. Архитектурная единица определяется не типом файла, а тем, кто владеет моделью, поведением, данными, runtime и lifecycle.
|
||||||
|
|
||||||
|
## Ответственность до размещения
|
||||||
|
|
||||||
|
**SLM-BASE-FND-001 - ОБЯЗАН.** Перед размещением кода необходимо определить его владельца, public boundary, runtime dependencies и lifecycle scope.
|
||||||
|
|
||||||
|
**SLM-BASE-FND-002 - СЛЕДУЕТ.** Код следует размещать в минимальном scope, который полностью владеет его ответственностью.
|
||||||
|
|
||||||
|
**SLM-BASE-FND-003 - ЗАПРЕЩЕНО.** Нельзя переносить код в общий слой или общий package только на основании предполагаемого будущего переиспользования.
|
||||||
|
|
||||||
|
## Путь продуктовых данных
|
||||||
|
|
||||||
|
Product data проходят через public boundary текущего владельца согласно [SLM-BASE-DATA-001](./state-and-data.md#product-gateway). Внешний сервис может оставаться физическим источником данных, но transport contract не становится product model автоматически.
|
||||||
|
|
||||||
|
## Явные зависимости
|
||||||
|
|
||||||
|
**SLM-BASE-FND-007 - ОБЯЗАН.** Runtime capabilities должны поступать владельцу поведения через разрешённые imports, явные arguments или contracts, а не через скрытый service locator или global mutable state.
|
||||||
|
|
||||||
|
**SLM-BASE-FND-008 - ЗАПРЕЩЕНО.** Type cast, barrel, alias, dynamic import или helper в `shared` не могут использоваться для обхода применимой архитектурной границы.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
Межмодульное взаимодействие и deep imports регулируются [SLM-BASE-API-001 - SLM-BASE-API-005](./public-api-and-imports.md#общие-правила).
|
||||||
|
|
||||||
|
## Scope и lifecycle
|
||||||
|
|
||||||
|
Создание, scope, activation и cleanup применимых runtimes и resources определены в [Runtime и lifecycle](./runtime-and-lifecycle.md).
|
||||||
|
|
||||||
|
## Overlays
|
||||||
|
|
||||||
|
Base SLM не вводит дополнительные архитектурные слои и специализированные runtime contracts. Каждый overlay самостоятельно определяет свои добавления и замены base-правил.
|
||||||
@@ -9,7 +9,7 @@ normative: true
|
|||||||
|
|
||||||
Эта директория содержит единый нормативный корпус SLM Design 2.0. Base SLM является законченной минимальной архитектурой; дополнительные ограничения подключаются независимыми overlays `SLM Advanced` или `SLM Pro`.
|
Эта директория содержит единый нормативный корпус SLM Design 2.0. Base SLM является законченной минимальной архитектурой; дополнительные ограничения подключаются независимыми overlays `SLM Advanced` или `SLM Pro`.
|
||||||
|
|
||||||
Пока статус равен `draft`, документы описывают проектируемую архитектуру и не заменяют действующую документацию в `docs/`.
|
Пока статус равен `draft`, документы описывают проектируемую архитектуру и не заменяют действующую документацию в `old-docs/`.
|
||||||
|
|
||||||
## Нормативный язык
|
## Нормативный язык
|
||||||
|
|
||||||
@@ -20,7 +20,9 @@ normative: true
|
|||||||
| `СЛЕДУЕТ` | Рекомендуемое решение; отступление требует явного обоснования |
|
| `СЛЕДУЕТ` | Рекомендуемое решение; отступление требует явного обоснования |
|
||||||
| `МОЖЕТ` | Допустимый, но необязательный вариант |
|
| `МОЖЕТ` | Допустимый, но необязательный вариант |
|
||||||
|
|
||||||
Правила имеют стабильные идентификаторы. Base использует формат `SLM-AREA-NNN`, Advanced - `SLM-ADV-AREA-NNN`, Pro - `SLM-PRO-AREA-NNN`. Точное нормативное требование принадлежит только той главе, где объявлен его rule ID.
|
Правила имеют стабильные идентификаторы. Base использует формат `SLM-BASE-AREA-NNN`, Advanced - `SLM-ADV-AREA-NNN`, Pro - `SLM-PRO-AREA-NNN`. Точное нормативное требование принадлежит только той главе, где объявлен его rule ID.
|
||||||
|
|
||||||
|
Все объявления доступны в [реестре правил](./rules.md). Для прямого перехода можно открыть поиск `Ctrl/⌘ K` и ввести полный rule ID.
|
||||||
|
|
||||||
## Architecture modes
|
## Architecture modes
|
||||||
|
|
||||||
@@ -33,11 +35,11 @@ SLM Pro = SLM + Pro rules
|
|||||||
|
|
||||||
## Приоритет
|
## Приоритет
|
||||||
|
|
||||||
**SLM-DOC-001 - ОБЯЗАН.** При конфликте между главами спецификации и любым ненормативным материалом приоритет имеет спецификация.
|
**SLM-BASE-DOC-001 - ОБЯЗАН.** При конфликте между главами спецификации и любым ненормативным материалом приоритет имеет спецификация.
|
||||||
|
|
||||||
**SLM-DOC-002 - ЗАПРЕЩЕНО.** Ненормативный документ не может вводить новое обязательное правило, исключение или архитектурную границу.
|
**SLM-BASE-DOC-002 - ЗАПРЕЩЕНО.** Ненормативный документ не может вводить новое обязательное правило, исключение или архитектурную границу.
|
||||||
|
|
||||||
**SLM-DOC-003 - ОБЯЗАН.** Изменение принятого архитектурного правила должно вноситься в главу, которая владеет соответствующим rule ID.
|
**SLM-BASE-DOC-003 - ОБЯЗАН.** Изменение принятого архитектурного правила должно вноситься в главу, которая владеет соответствующим rule ID.
|
||||||
|
|
||||||
## Base SLM
|
## Base SLM
|
||||||
|
|
||||||
@@ -21,26 +21,26 @@ normative: true
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
**SLM-APP-001 - ОБЯЗАН.** Route entry должен оставаться тонким adapter, нормализующим framework input и делегирующим готовому composition module.
|
**SLM-BASE-APP-001 - ОБЯЗАН.** Route entry должен оставаться тонким adapter, нормализующим framework input и делегирующим готовому composition module.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
framework route
|
framework route
|
||||||
→ composition entry
|
→ composition entry
|
||||||
```
|
```
|
||||||
|
|
||||||
**SLM-APP-002 - ЗАПРЕЩЕНО.** `app` не может владеть product page, screen, widget, product scenario, store или application wiring.
|
**SLM-BASE-APP-002 - ЗАПРЕЩЕНО.** `app` не может владеть product page, screen, widget, product scenario, store или application wiring.
|
||||||
|
|
||||||
**SLM-APP-003 - ЗАПРЕЩЕНО.** Route entry не должен напрямую собирать product integrations, вызывать SDK или формировать product model.
|
**SLM-BASE-APP-003 - ЗАПРЕЩЕНО.** Route entry не должен напрямую собирать product integrations, вызывать SDK или формировать product model.
|
||||||
|
|
||||||
**SLM-APP-004 - ОБЯЗАН.** Framework-specific input должен быть считан в `app` и передан вниз в минимальной нормализованной форме.
|
**SLM-BASE-APP-004 - ОБЯЗАН.** Framework-specific input должен быть считан в `app` и передан вниз в минимальной нормализованной форме.
|
||||||
|
|
||||||
Механическая нормализация включает извлечение route params, headers и framework wrappers. Product validation, создание value objects и выбор product outcome остаются у владельца product semantics.
|
Механическая нормализация включает извлечение route params, headers и framework wrappers. Product validation, создание value objects и выбор product outcome остаются у владельца product semantics.
|
||||||
|
|
||||||
Запрет другим SLM-слоям импортировать `app` определяется base-правилом `SLM-ARCH-003`.
|
Запрет другим SLM-слоям импортировать `app` определяется base-правилом `SLM-BASE-ARCH-003`.
|
||||||
|
|
||||||
**SLM-APP-006 - СЛЕДУЕТ.** Framework behavior, которому нужны product dependencies или product UI, следует реализовать готовым composition entry и только подключить из `app`.
|
**SLM-BASE-APP-006 - СЛЕДУЕТ.** Framework behavior, которому нужны product dependencies или product UI, следует реализовать готовым composition entry и только подключить из `app`.
|
||||||
|
|
||||||
**SLM-APP-007 - МОЖЕТ.** `app` может напрямую импортировать framework APIs и static/global resources из `shared`, если framework требует подключить их в root entry.
|
**SLM-BASE-APP-007 - МОЖЕТ.** `app` может напрямую импортировать framework APIs и static/global resources из `shared`, если framework требует подключить их в root entry.
|
||||||
|
|
||||||
## Допустимая структура
|
## Допустимая структура
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ app/
|
|||||||
|
|
||||||
## Примеры нарушений
|
## Примеры нарушений
|
||||||
|
|
||||||
Следующие сущности являются примерами нарушений `SLM-APP-002` и `SLM-APP-003`:
|
Следующие сущности являются примерами нарушений `SLM-BASE-APP-002` и `SLM-BASE-APP-003`:
|
||||||
|
|
||||||
- `ProductPage`;
|
- `ProductPage`;
|
||||||
- product Provider;
|
- product Provider;
|
||||||
@@ -36,17 +36,17 @@ compositions/
|
|||||||
|
|
||||||
## Product ownership
|
## Product ownership
|
||||||
|
|
||||||
**SLM-CMP-001 - ОБЯЗАН.** Product flow и его локальная product logic должны принадлежать минимальной composition, охватывающей всех consumers этой ответственности.
|
**SLM-BASE-CMP-001 - ОБЯЗАН.** Product flow и его локальная product logic должны принадлежать минимальной composition, охватывающей всех consumers этой ответственности.
|
||||||
|
|
||||||
Composition может использовать public API `infra` для external operations, сохраняя product mapping, outcomes и fallback semantics у себя.
|
Composition может использовать public API `infra` для external operations, сохраняя product mapping, outcomes и fallback semantics у себя.
|
||||||
|
|
||||||
## Public boundaries
|
## Public boundaries
|
||||||
|
|
||||||
**SLM-CMP-005 - ЗАПРЕЩЕНО.** Composition не может импортировать private services, integrations, stores, Context или другие internal paths используемого module.
|
**SLM-BASE-CMP-005 - ЗАПРЕЩЕНО.** Composition не может импортировать private services, integrations, stores, Context или другие internal paths используемого module.
|
||||||
|
|
||||||
## Product UI
|
## Product UI
|
||||||
|
|
||||||
**SLM-CMP-006 - ОБЯЗАН.** UI, объединяющий несколько самостоятельных modules, route/page scope либо application flow, принадлежит `compositions`.
|
**SLM-BASE-CMP-006 - ОБЯЗАН.** UI, объединяющий несколько самостоятельных modules, route/page scope либо application flow, принадлежит `compositions`.
|
||||||
|
|
||||||
Примеры:
|
Примеры:
|
||||||
|
|
||||||
@@ -56,11 +56,11 @@ Composition может использовать public API `infra` для extern
|
|||||||
- route guard с navigation outcome;
|
- route guard с navigation outcome;
|
||||||
- widget, использующий public APIs двух самостоятельных modules.
|
- widget, использующий public APIs двух самостоятельных modules.
|
||||||
|
|
||||||
**SLM-CMP-007 - МОЖЕТ.** Composition может использовать product UI, опубликованный другими modules, и universal UI, передавая props, callbacks и slots.
|
**SLM-BASE-CMP-007 - МОЖЕТ.** Composition может использовать product UI, опубликованный другими modules, и universal UI, передавая props, callbacks и slots.
|
||||||
|
|
||||||
## State
|
## State
|
||||||
|
|
||||||
**SLM-CMP-008 - ОБЯЗАН.** Page-local presentation state принадлежит минимальной composition, охватывающей всех его consumers.
|
**SLM-BASE-CMP-008 - ОБЯЗАН.** Page-local presentation state принадлежит минимальной composition, охватывающей всех его consumers.
|
||||||
|
|
||||||
Примеры page-local state:
|
Примеры page-local state:
|
||||||
|
|
||||||
@@ -70,15 +70,15 @@ Composition может использовать public API `infra` для extern
|
|||||||
- presentation filters;
|
- presentation filters;
|
||||||
- состояние раскрытия section.
|
- состояние раскрытия section.
|
||||||
|
|
||||||
**SLM-CMP-009 - ЗАПРЕЩЕНО.** Page store не может становиться параллельным владельцем product model или canonical product cache другого owner.
|
**SLM-BASE-CMP-009 - ЗАПРЕЩЕНО.** Page store не может становиться параллельным владельцем product model или canonical product cache другого owner.
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
|
|
||||||
**SLM-CMP-010 - МОЖЕТ.** Composition module может импортировать public API других composition modules, infra, ui и shared.
|
**SLM-BASE-CMP-010 - МОЖЕТ.** Composition module может импортировать public API других composition modules, infra, ui и shared.
|
||||||
|
|
||||||
Runtime-циклы между composition modules запрещены base-правилом `SLM-API-016`.
|
Runtime-циклы между composition modules запрещены base-правилом `SLM-BASE-API-016`.
|
||||||
|
|
||||||
**SLM-CMP-015 - ОБЯЗАН.** Client и server composition entries должны иметь раздельные public entrypoints и environment markers, если composition участвует в обоих runtime graphs.
|
**SLM-BASE-CMP-015 - ОБЯЗАН.** Client и server composition entries должны иметь раздельные public entrypoints и environment markers, если composition участвует в обоих runtime graphs.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
@@ -20,15 +20,15 @@ normative: true
|
|||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
**SLM-LAY-001 - ОБЯЗАН.** Module должен располагаться в слое, который владеет его основной ответственностью.
|
**SLM-BASE-LAY-001 - ОБЯЗАН.** Module должен располагаться в слое, который владеет его основной ответственностью.
|
||||||
|
|
||||||
**SLM-LAY-002 - ЗАПРЕЩЕНО.** Нельзя выбирать слой по техническому типу файла без определения владельца поведения и данных.
|
**SLM-BASE-LAY-002 - ЗАПРЕЩЕНО.** Нельзя выбирать слой по техническому типу файла без определения владельца поведения и данных.
|
||||||
|
|
||||||
**SLM-LAY-003 - ОБЯЗАН.** Межслойный import должен одновременно соответствовать общей dependency direction и public API импортируемого module.
|
**SLM-BASE-LAY-003 - ОБЯЗАН.** Межслойный import должен одновременно соответствовать общей dependency direction и public API импортируемого module.
|
||||||
|
|
||||||
**SLM-LAY-004 - ЗАПРЕЩЕНО.** Нельзя создавать proxy module в разрешённом слое только для обхода запрещённого направления import.
|
**SLM-BASE-LAY-004 - ЗАПРЕЩЕНО.** Нельзя создавать proxy module в разрешённом слое только для обхода запрещённого направления import.
|
||||||
|
|
||||||
**SLM-LAY-005 - СЛЕДУЕТ.** При смешанной ответственности module следует разделить по реальным владельцам. Application flow и UI нескольких самостоятельных modules следует собирать в `compositions`.
|
**SLM-BASE-LAY-005 - СЛЕДУЕТ.** При смешанной ответственности module следует разделить по реальным владельцам. Application flow и UI нескольких самостоятельных modules следует собирать в `compositions`.
|
||||||
|
|
||||||
## Выбор слоя
|
## Выбор слоя
|
||||||
|
|
||||||
53
docs/ru/specification/layers/infra.md
Normal file
53
docs/ru/specification/layers/infra.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
title: Слой Infra
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Слой Infra
|
||||||
|
|
||||||
|
`infra` содержит technical capabilities приложения, не определяющие product model и scenarios.
|
||||||
|
|
||||||
|
## Примеры modules
|
||||||
|
|
||||||
|
```text
|
||||||
|
infra/
|
||||||
|
├── http/
|
||||||
|
├── backend-api/
|
||||||
|
├── realtime/
|
||||||
|
├── analytics/
|
||||||
|
├── logger/
|
||||||
|
├── app-config/
|
||||||
|
├── storage/
|
||||||
|
├── i18n/
|
||||||
|
└── theme/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
**SLM-BASE-INF-001 - ОБЯЗАН.** Infra module должен описывать technical capability, а не product semantics или scenario.
|
||||||
|
|
||||||
|
**SLM-BASE-INF-002 - МОЖЕТ.** Infra module может импортировать public API другого infra module и `shared`.
|
||||||
|
|
||||||
|
Запрет infra импортировать `compositions` или `app` определяется base-правилом `SLM-BASE-ARCH-003`.
|
||||||
|
|
||||||
|
**SLM-BASE-INF-004 - ЗАПРЕЩЕНО.** Infra не может владеть product wiring, собирать application graph или предоставлять generic product service locator.
|
||||||
|
|
||||||
|
**SLM-BASE-INF-005 - ЗАПРЕЩЕНО.** Infra не создаёт product errors, product fallback и product model из transport DTO.
|
||||||
|
|
||||||
|
**SLM-BASE-INF-006 - МОЖЕТ.** Infra может экспортировать technical client, transport, event source, storage primitive или platform wrapper через собственный public API.
|
||||||
|
|
||||||
|
**SLM-BASE-INF-007 - ОБЯЗАН.** Generated SDK и transport details должны оставаться внутри technical или private integration boundary владельца и не становиться частью public product contract.
|
||||||
|
|
||||||
|
## Product integration
|
||||||
|
|
||||||
|
Infra знает technical mechanism:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HTTP client
|
||||||
|
WebSocket transport
|
||||||
|
local storage primitive
|
||||||
|
analytics SDK
|
||||||
|
```
|
||||||
|
|
||||||
|
Product owner определяет semantics использования capability; infra предоставляет механизм через public API. Один infra module может использоваться несколькими product owners без знания их semantics.
|
||||||
42
docs/ru/specification/layers/shared.md
Normal file
42
docs/ru/specification/layers/shared.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
title: Слой Shared
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Слой Shared
|
||||||
|
|
||||||
|
`shared` является детерминированным фундаментом приложения и не знает о SLM-модулях верхних слоёв.
|
||||||
|
|
||||||
|
## Допустимое содержимое
|
||||||
|
|
||||||
|
- pure utilities;
|
||||||
|
- value predicates;
|
||||||
|
- product-agnostic types;
|
||||||
|
- styling foundation и tokens;
|
||||||
|
- static resources;
|
||||||
|
- compile-time constants без product ownership;
|
||||||
|
- deterministic formatting primitives.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
**SLM-BASE-SHR-001 - ОБЯЗАН.** Результат shared utility должен определяться явными аргументами и не зависеть от скрытого runtime environment.
|
||||||
|
|
||||||
|
**SLM-BASE-SHR-002 - ЗАПРЕЩЕНО.** `shared` не может импортировать `app`, `compositions`, `infra` или `ui`.
|
||||||
|
|
||||||
|
**SLM-BASE-SHR-003 - ЗАПРЕЩЕНО.** `shared` не может владеть product types, product rules, runtime state, I/O, storage access или event subscriptions.
|
||||||
|
|
||||||
|
**SLM-BASE-SHR-004 - ЗАПРЕЩЕНО.** Нельзя переносить product helper, DTO, integration contract или product config в `shared` для обхода import boundary.
|
||||||
|
|
||||||
|
**SLM-BASE-SHR-005 - СЛЕДУЕТ.** Код следует поднимать в `shared` только при подтверждённой product-agnostic semantics, а не из-за повторения нескольких строк.
|
||||||
|
|
||||||
|
## Отличие от других слоёв
|
||||||
|
|
||||||
|
| Код | Владелец |
|
||||||
|
|---|---|
|
||||||
|
| Email validator с product rules | Владеющий product module |
|
||||||
|
| Generic string trim utility | `shared` |
|
||||||
|
| Browser storage wrapper | `infra` |
|
||||||
|
| Product storage integration | Product owner; storage primitive - `infra` |
|
||||||
|
| UI spacing tokens | `shared` |
|
||||||
|
| Button consuming spacing tokens | `ui` |
|
||||||
48
docs/ru/specification/layers/ui.md
Normal file
48
docs/ru/specification/layers/ui.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: Слой UI
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Слой UI
|
||||||
|
|
||||||
|
`ui` содержит reusable presentation modules без product scenario и product ownership.
|
||||||
|
|
||||||
|
## Примеры
|
||||||
|
|
||||||
|
```text
|
||||||
|
ui/
|
||||||
|
├── button/
|
||||||
|
├── input/
|
||||||
|
├── icon/
|
||||||
|
├── modal/
|
||||||
|
├── carousel/
|
||||||
|
├── tabs/
|
||||||
|
└── tooltip/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
**SLM-BASE-UI-001 - ОБЯЗАН.** UI module должен быть применим без product-specific knowledge.
|
||||||
|
|
||||||
|
**SLM-BASE-UI-002 - ЗАПРЕЩЕНО.** UI module не может импортировать `compositions`, `app` или product-specific infra.
|
||||||
|
|
||||||
|
**SLM-BASE-UI-003 - МОЖЕТ.** UI module может импортировать public API других UI modules и `shared`.
|
||||||
|
|
||||||
|
**SLM-BASE-UI-004 - ЗАПРЕЩЕНО.** UI module не выбирает product data source, не вызывает product scenario и не владеет multi-module behavior.
|
||||||
|
|
||||||
|
**SLM-BASE-UI-005 - МОЖЕТ.** UI module может владеть локальным interaction state, необходимым только для собственной presentation mechanics.
|
||||||
|
|
||||||
|
**SLM-BASE-UI-006 - ОБЯЗАН.** Компонент с product semantics должен принадлежать владеющему product module, а не `ui`.
|
||||||
|
|
||||||
|
## Классификация
|
||||||
|
|
||||||
|
| Сущность | Владелец |
|
||||||
|
|---|---|
|
||||||
|
| `Button`, `Input`, `Modal` | `ui` |
|
||||||
|
| `LoginForm` одной auth responsibility | Владеющий product module |
|
||||||
|
| Application header | `compositions` |
|
||||||
|
| Generic date picker | `ui` |
|
||||||
|
| Medication schedule | Владеющий product module согласно ownership |
|
||||||
|
|
||||||
|
Универсальность определяется отсутствием product knowledge, а не количеством текущих consumers.
|
||||||
@@ -68,7 +68,7 @@ domains/knv/auth/
|
|||||||
|
|
||||||
Это пример, а не обязательный scaffold. Небольшой domain может состоять из одного файла и public entrypoint.
|
Это пример, а не обязательный scaffold. Небольшой domain может состоять из одного файла и public entrypoint.
|
||||||
|
|
||||||
Domain может хранить файлы в корне и использовать любые необходимые segments согласно base-правилам [SLM-SEG-001 - SLM-SEG-003](../../segments.md#правила).
|
Domain может хранить файлы в корне и использовать любые необходимые segments согласно base-правилам [SLM-BASE-SEG-001 - SLM-BASE-SEG-003](../../segments.md#правила).
|
||||||
|
|
||||||
**SLM-ADV-DOM-006 - ЗАПРЕЩЕНО.** Нельзя создавать пустые segments или копировать полную структуру другого domain без текущей ответственности.
|
**SLM-ADV-DOM-006 - ЗАПРЕЩЕНО.** Нельзя создавать пустые segments или копировать полную структуру другого domain без текущей ответственности.
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ Domain может хранить файлы в корне и использов
|
|||||||
|
|
||||||
## Public API
|
## Public API
|
||||||
|
|
||||||
Public boundary Advanced domain следует base-правилам `SLM-API-001` и `SLM-API-002`.
|
Public boundary Advanced domain следует base-правилам `SLM-BASE-API-001` и `SLM-BASE-API-002`.
|
||||||
|
|
||||||
**SLM-ADV-DOM-009 - МОЖЕТ.** Public API domain может экспортировать выбранные командой hooks, Providers, Context, components, service APIs, store access APIs и types как стабильный contract.
|
**SLM-ADV-DOM-009 - МОЖЕТ.** Public API domain может экспортировать выбранные командой hooks, Providers, Context, components, service APIs, store access APIs и types как стабильный contract.
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ domain -> domain | infra | ui | shared
|
|||||||
|
|
||||||
**SLM-ADV-DOM-012 - МОЖЕТ.** Domain может напрямую использовать public API `infra`, `ui` и `shared` без обязательной промежуточной abstraction.
|
**SLM-ADV-DOM-012 - МОЖЕТ.** Domain может напрямую использовать public API `infra`, `ui` и `shared` без обязательной промежуточной abstraction.
|
||||||
|
|
||||||
Runtime cycles запрещены base-правилом `SLM-API-016`.
|
Runtime cycles запрещены base-правилом `SLM-BASE-API-016`.
|
||||||
|
|
||||||
**SLM-ADV-DOM-013 - ЗАПРЕЩЕНО.** Type-only dependency cycle между domains запрещён, даже если runtime graph остаётся ацикличным.
|
**SLM-ADV-DOM-013 - ЗАПРЕЩЕНО.** Type-only dependency cycle между domains запрещён, даже если runtime graph остаётся ацикличным.
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ composition
|
|||||||
|
|
||||||
**SLM-ADV-DOM-015 - МОЖЕТ.** Product UI одной domain responsibility может принадлежать этому domain.
|
**SLM-ADV-DOM-015 - МОЖЕТ.** Product UI одной domain responsibility может принадлежать этому domain.
|
||||||
|
|
||||||
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-CMP-006`.
|
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-BASE-CMP-006`.
|
||||||
|
|
||||||
## Monorepo boundary
|
## Monorepo boundary
|
||||||
|
|
||||||
@@ -49,11 +49,11 @@ Base dependency direction для остальных слоёв сохраняе
|
|||||||
|
|
||||||
## Изменение product ownership
|
## Изменение product ownership
|
||||||
|
|
||||||
**SLM-ADV-CMP-001 - ОБЯЗАН.** Если product responsibility получила domain owner, Advanced заменяет для этой ответственности base-правило `SLM-CMP-001`: domain владеет собственной product logic, а composition владеет application flow и связывает public APIs.
|
**SLM-ADV-CMP-001 - ОБЯЗАН.** Если product responsibility получила domain owner, Advanced заменяет для этой ответственности base-правило `SLM-BASE-CMP-001`: domain владеет собственной product logic, а composition владеет application flow и связывает public APIs.
|
||||||
|
|
||||||
Product logic без domain owner продолжает следовать base SLM и принадлежит минимальной composition.
|
Product logic без domain owner продолжает следовать base SLM и принадлежит минимальной composition.
|
||||||
|
|
||||||
**SLM-ADV-CMP-010 - МОЖЕТ.** Composition module может импортировать public API Advanced domains в дополнение к imports, разрешённым base-правилом `SLM-CMP-010`.
|
**SLM-ADV-CMP-010 - МОЖЕТ.** Composition module может импортировать public API Advanced domains в дополнение к imports, разрешённым base-правилом `SLM-BASE-CMP-010`.
|
||||||
|
|
||||||
## Advanced Domain Specification
|
## Advanced Domain Specification
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ Server technical inputs ограничены request/framework data, server envi
|
|||||||
|
|
||||||
Assembly определяет способ создания, но не владеет полным cross-domain graph.
|
Assembly определяет способ создания, но не владеет полным cross-domain graph.
|
||||||
|
|
||||||
Отсутствие product request, socket connection и background resource при вызове runtime creator определяется base-правилом `SLM-LIFE-002`.
|
Отсутствие product request, socket connection и background resource при вызове runtime creator определяется base-правилом `SLM-BASE-LIFE-002`.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
module import
|
module import
|
||||||
@@ -100,6 +100,6 @@ explicit start
|
|||||||
|
|
||||||
Client и server runtimes являются разными instances над общей business semantics.
|
Client и server runtimes являются разными instances над общей business semantics.
|
||||||
|
|
||||||
Запрет на передачу DomainRuntime, functions, Context, store или query client через serializable server/client boundary определяется правилом [SLM-DATA-012](../../../state-and-data.md#serializable-boundaries).
|
Запрет на передачу DomainRuntime, functions, Context, store или query client через serializable server/client boundary определяется правилом [SLM-BASE-DATA-012](../../../state-and-data.md#serializable-boundaries).
|
||||||
|
|
||||||
**SLM-PRO-ASM-014 - МОЖЕТ.** Server может передать client assembly только serializable business-owned bootstrap data без secrets и mutable runtime objects.
|
**SLM-PRO-ASM-014 - МОЖЕТ.** Server может передать client assembly только serializable business-owned bootstrap data без secrets и mutable runtime objects.
|
||||||
@@ -70,7 +70,7 @@ Domain React UI может:
|
|||||||
|
|
||||||
**SLM-PRO-FRM-013 - ЗАПРЕЩЕНО.** Domain UI не может импортировать runtime другого domain или оркестрировать route/page flow.
|
**SLM-PRO-FRM-013 - ЗАПРЕЩЕНО.** Domain UI не может импортировать runtime другого domain или оркестрировать route/page flow.
|
||||||
|
|
||||||
Владение React UI, использующим несколько domains, определено base-правилом [SLM-CMP-006](../../../layers/compositions.md#product-ui).
|
Владение React UI, использующим несколько domains, определено base-правилом [SLM-BASE-CMP-006](../../../layers/compositions.md#product-ui).
|
||||||
|
|
||||||
## Client boundary
|
## Client boundary
|
||||||
|
|
||||||
@@ -52,11 +52,11 @@ Pro domain может владеть:
|
|||||||
|
|
||||||
**SLM-PRO-DOM-005 - ЗАПРЕЩЕНО.** Domain не может владеть framework route entry, page/layout composition, UI нескольких самостоятельных product responsibilities, universal technical capability или product-agnostic UI primitive.
|
**SLM-PRO-DOM-005 - ЗАПРЕЩЕНО.** Domain не может владеть framework route entry, page/layout composition, UI нескольких самостоятельных product responsibilities, universal technical capability или product-agnostic UI primitive.
|
||||||
|
|
||||||
Public entrypoints Pro domain следуют base-правилам `SLM-API-001` и `SLM-API-002`; Pro-главы вводят дополнительные ограничения exports.
|
Public entrypoints Pro domain следуют base-правилам `SLM-BASE-API-001` и `SLM-BASE-API-002`; Pro-главы вводят дополнительные ограничения exports.
|
||||||
|
|
||||||
**SLM-PRO-DOM-007 - ЗАПРЕЩЕНО.** Если product responsibility получила Pro domain owner, app, composition или infra не могут создавать параллельную модель этой ответственности либо обходить её public boundary.
|
**SLM-PRO-DOM-007 - ЗАПРЕЩЕНО.** Если product responsibility получила Pro domain owner, app, composition или infra не могут создавать параллельную модель этой ответственности либо обходить её public boundary.
|
||||||
|
|
||||||
**SLM-PRO-DOM-008 - ОБЯЗАН.** Для domain-owned responsibility это правило заменяет base-правило `SLM-CMP-001`: business владеет product logic, а composition владеет application flow и runtime graph.
|
**SLM-PRO-DOM-008 - ОБЯЗАН.** Для domain-owned responsibility это правило заменяет base-правило `SLM-BASE-CMP-001`: business владеет product logic, а composition владеет application flow и runtime graph.
|
||||||
|
|
||||||
Product responsibility считается устойчивой, если имеет самостоятельную product model или transitions, используется несколькими application flows либо владеет external integration/lifecycle contract.
|
Product responsibility считается устойчивой, если имеет самостоятельную product model или transitions, используется несколькими application flows либо владеет external integration/lifecycle contract.
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ Stateless logic API также является DomainRuntime, если он с
|
|||||||
|
|
||||||
**SLM-PRO-DOM-014 - МОЖЕТ.** Product UI одной Pro domain responsibility может принадлежать framework surface этого domain.
|
**SLM-PRO-DOM-014 - МОЖЕТ.** Product UI одной Pro domain responsibility может принадлежать framework surface этого domain.
|
||||||
|
|
||||||
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-CMP-006`.
|
UI нескольких самостоятельных responsibilities остаётся в `compositions` согласно base-правилу `SLM-BASE-CMP-006`.
|
||||||
|
|
||||||
## Cross-domain graph
|
## Cross-domain graph
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ domains -> согласно внутренним Pro zones
|
|||||||
|
|
||||||
Base dependency direction для остальных слоёв сохраняется.
|
Base dependency direction для остальных слоёв сохраняется.
|
||||||
|
|
||||||
**SLM-PRO-CMP-010 - МОЖЕТ.** Composition module может импортировать public entrypoints Pro domains в дополнение к imports, разрешённым base-правилом `SLM-CMP-010`.
|
**SLM-PRO-CMP-010 - МОЖЕТ.** Composition module может импортировать public entrypoints Pro domains в дополнение к imports, разрешённым base-правилом `SLM-BASE-CMP-010`.
|
||||||
|
|
||||||
## Pro Domain Specification
|
## Pro Domain Specification
|
||||||
|
|
||||||
72
docs/ru/specification/modules-and-groups.md
Normal file
72
docs/ru/specification/modules-and-groups.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
title: Модули и группы
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Модули и Группы
|
||||||
|
|
||||||
|
## Module
|
||||||
|
|
||||||
|
Module является минимальным самостоятельным владельцем ответственности и предоставляет public boundary внешнему коду.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-001 - ОБЯЗАН.** Module должен иметь одну сформулированную ответственность и одного архитектурного owner.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-002 - ОБЯЗАН.** Внешний consumer взаимодействует с module только через его public API.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-003 - СЛЕДУЕТ.** Module следует ограничивать только теми внутренними parts и segments, которые необходимы текущей ответственности.
|
||||||
|
|
||||||
|
Типичные modules:
|
||||||
|
|
||||||
|
- page, layout, screen или widget в `compositions`;
|
||||||
|
- technical service в `infra`;
|
||||||
|
- reusable UI module в `ui`.
|
||||||
|
|
||||||
|
`app` содержит framework entries и не обязан организовываться как SLM modules. `shared` может содержать небольшие public units, но не runtime modules.
|
||||||
|
|
||||||
|
## Group
|
||||||
|
|
||||||
|
Group классифицирует modules и другие groups, но не владеет поведением.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-004 - ЗАПРЕЩЕНО.** Group не может иметь `index.ts`, public API, state, runtime, dependencies или assembly.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-005 - ЗАПРЕЩЕНО.** Внешний код не может импортировать group path.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-006 - МОЖЕТ.** Group может содержать другие groups и конечные modules.
|
||||||
|
|
||||||
|
```text
|
||||||
|
compositions/
|
||||||
|
└── pages/ # group
|
||||||
|
├── home/ # composition module
|
||||||
|
└── profile/ # composition module
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component
|
||||||
|
|
||||||
|
Component является presentation unit внутри module и не считается самостоятельным архитектурным owner.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-008 - ЗАПРЕЩЕНО.** Component не может самостоятельно выбирать application-level product source, выполнять module wiring или оркестрировать несколько самостоятельных modules.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-009 - МОЖЕТ.** Component может владеть локальной presentation mechanics и рендерить другие components, разрешённые слоем владельца.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-010 - ОБЯЗАН.** Presentation unit с самостоятельной ответственностью, внешними архитектурными dependencies или внутренней modular structure должна оформляться как module или nested module. Сам факт локального hook/state не делает component модулем.
|
||||||
|
|
||||||
|
## Nested module
|
||||||
|
|
||||||
|
Самостоятельная часть родительского module может быть оформлена nested module, если имеет собственную ответственность и public boundary только внутри родителя.
|
||||||
|
|
||||||
|
```text
|
||||||
|
compositions/pages/home/
|
||||||
|
└── parts/
|
||||||
|
└── hero-section/
|
||||||
|
├── hero-section.tsx
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-011 - ЗАПРЕЩЕНО.** Nested module не может использоваться для сокрытия ответственности, которой фактически владеет другой module или layer.
|
||||||
|
|
||||||
|
## Scope evolution
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-012 - СЛЕДУЕТ.** Код следует поднимать из локального owner в более широкий module только после появления реального совместного consumer или общей ответственности.
|
||||||
|
|
||||||
|
**SLM-BASE-MOD-013 - ЗАПРЕЩЕНО.** Физическое повторение само по себе не доказывает общий ownership.
|
||||||
54
docs/ru/specification/monorepo.md
Normal file
54
docs/ru/specification/monorepo.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: Монорепозитории
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Монорепозитории
|
||||||
|
|
||||||
|
SLM применяется внутри границы каждого frontend-приложения. Workspace packages имеют собственные public boundaries и ownership.
|
||||||
|
|
||||||
|
## Application boundary
|
||||||
|
|
||||||
|
```text
|
||||||
|
apps/
|
||||||
|
└── web/
|
||||||
|
└── src/
|
||||||
|
├── app/
|
||||||
|
├── compositions/
|
||||||
|
├── infra/
|
||||||
|
├── ui/
|
||||||
|
└── shared/
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-001 - ОБЯЗАН.** Каждое приложение должно самостоятельно определять свои application compositions, product ownership и runtime wiring.
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-002 - ЗАПРЕЩЕНО.** Workspace package не может импортировать код из `apps/*`.
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-003 - ЗАПРЕЩЕНО.** Одно приложение не может deep-import исходники другого приложения вместо общего package contract.
|
||||||
|
|
||||||
|
## Package boundary
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-004 - ОБЯЗАН.** Package должен иметь самостоятельного owner, public exports и подтверждённую reuse/ownership semantics.
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-005 - ЗАПРЕЩЕНО.** Нельзя создавать package только для обхода layer direction, public API или иной объявленной dependency boundary.
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-006 - ОБЯЗАН.** Consumers импортируют package через объявленный package export, а не через filesystem path к internal source.
|
||||||
|
|
||||||
|
## Типичные packages
|
||||||
|
|
||||||
|
Допустимыми кандидатами являются:
|
||||||
|
|
||||||
|
- product-agnostic UI kit;
|
||||||
|
- technical infra client;
|
||||||
|
- deterministic shared foundation;
|
||||||
|
- schema/codegen/tooling package;
|
||||||
|
- configuration package без application-specific wiring.
|
||||||
|
|
||||||
|
Base SLM не присваивает package дополнительный архитектурный статус автоматически.
|
||||||
|
|
||||||
|
## Dependency direction
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-009 - ОБЯЗАН.** Package dependency graph должен оставаться ацикличным и соответствовать заявленной ответственности packages.
|
||||||
|
|
||||||
|
**SLM-BASE-MONO-010 - ЗАПРЕЩЕНО.** Shared package не может импортировать application composition или app-specific infra package.
|
||||||
55
docs/ru/specification/public-api-and-imports.md
Normal file
55
docs/ru/specification/public-api-and-imports.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: Public API и импорты
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Public API и Импорты
|
||||||
|
|
||||||
|
Public API ограничивает знание consumers о внутренней структуре module. Точная форма entrypoint определяется владельцем и не требует обязательного `index.ts`.
|
||||||
|
|
||||||
|
## Общие правила
|
||||||
|
|
||||||
|
**SLM-BASE-API-001 - ОБЯЗАН.** Межмодульный import должен использовать объявленный public entrypoint импортируемого module.
|
||||||
|
|
||||||
|
**SLM-BASE-API-002 - ЗАПРЕЩЕНО.** Deep imports во внутренние segments, files и иные private paths другого module запрещены.
|
||||||
|
|
||||||
|
**SLM-BASE-API-003 - ОБЯЗАН.** Каждый runtime export должен иметь реального consumer за пределами владеющего entrypoint и стабильную ответственность.
|
||||||
|
|
||||||
|
**SLM-BASE-API-004 - ЗАПРЕЩЕНО.** Public API не может случайно раскрывать implementation unit, который владелец считает private или lifecycle которого не является частью public contract.
|
||||||
|
|
||||||
|
**SLM-BASE-API-005 - ОБЯЗАН.** Alias или package subpath должен физически разрешаться TypeScript, tests и production build.
|
||||||
|
|
||||||
|
**SLM-BASE-API-009 - МОЖЕТ.** Public entrypoint может быть root `index.ts`, отдельным named entry, package export или другим явно объявленным path.
|
||||||
|
|
||||||
|
**SLM-BASE-API-010 - ОБЯЗАН.** Public и private paths module должны быть различимы consumers и repository tooling.
|
||||||
|
|
||||||
|
## Layer matrix
|
||||||
|
|
||||||
|
| Importer | Runtime imports |
|
||||||
|
|---|---|
|
||||||
|
| `app` | Public composition entries, shared static/global resources |
|
||||||
|
| `compositions` | Compositions, infra, ui, shared |
|
||||||
|
| `infra` | Infra, shared |
|
||||||
|
| `ui` | UI, shared |
|
||||||
|
| `shared` | External pure libraries only |
|
||||||
|
|
||||||
|
## Type-only imports
|
||||||
|
|
||||||
|
**SLM-BASE-API-006 - МОЖЕТ.** `import type` может использоваться для разрешённого contract dependency без создания runtime edge.
|
||||||
|
|
||||||
|
**SLM-BASE-API-007 - ЗАПРЕЩЕНО.** Type-only import не разрешает перенос ownership, импорт private concrete runtime type или обход layer boundary.
|
||||||
|
|
||||||
|
## Groups
|
||||||
|
|
||||||
|
Отсутствие public entrypoint у group определяется base-правилом `SLM-BASE-MOD-004`.
|
||||||
|
|
||||||
|
**SLM-BASE-API-015 - ОБЯЗАН.** Composition public API экспортирует только entry components, access APIs, types и contracts, необходимые внешним composition consumers.
|
||||||
|
|
||||||
|
## Cycles
|
||||||
|
|
||||||
|
**SLM-BASE-API-016 - ЗАПРЕЩЕНО.** Runtime import cycle между modules запрещён независимо от того, способен ли bundler его выполнить.
|
||||||
|
|
||||||
|
**SLM-BASE-API-017 - ЗАПРЕЩЕНО.** Barrel не должен создавать скрытый cycle между ready composition и access API её children.
|
||||||
|
|
||||||
|
Дополнительные entrypoints и import restrictions принадлежат overlay, который их вводит.
|
||||||
13
docs/ru/specification/rules.md
Normal file
13
docs/ru/specification/rules.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: Реестр правил
|
||||||
|
status: draft
|
||||||
|
normative: false
|
||||||
|
search: false
|
||||||
|
aside: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Реестр Правил
|
||||||
|
|
||||||
|
Реестр формируется автоматически из нормативных объявлений Specification. Для быстрого перехода к известному ID также можно открыть поиск `Ctrl/⌘ K`, ввести полный идентификатор и нажать `Enter`.
|
||||||
|
|
||||||
|
<RuleCatalog />
|
||||||
83
docs/ru/specification/runtime-and-lifecycle.md
Normal file
83
docs/ru/specification/runtime-and-lifecycle.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
---
|
||||||
|
title: Runtime и lifecycle
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Runtime и Lifecycle
|
||||||
|
|
||||||
|
Lifecycle является архитектурной частью любого mutable runtime, subscription и external resource. Эти правила не требуют создавать отдельный runtime или factory, если у module нет соответствующего состояния или resources.
|
||||||
|
|
||||||
|
## Definition, creation и activation
|
||||||
|
|
||||||
|
Для module с создаваемым runtime применима модель:
|
||||||
|
|
||||||
|
```text
|
||||||
|
definition
|
||||||
|
-> module объявляет creator
|
||||||
|
|
||||||
|
creation
|
||||||
|
-> creator создаёт instance без external effects
|
||||||
|
|
||||||
|
activation
|
||||||
|
-> scope owner запускает resources и получает cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-001 - ЗАПРЕЩЕНО.** Module import не должен выполнять product I/O, открывать connection или регистрировать global listener.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-002 - ОБЯЗАН.** Если module предоставляет factory или runtime creator, creation должна быть side-effect free относительно external resources.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-003 - ОБЯЗАН.** Subscription, socket, timer и listener запускаются явной operation владельца scope.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-004 - ОБЯЗАН.** Каждый запущенный resource должен иметь cleanup или dispose contract.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
| Scope | Примеры владельца |
|
||||||
|
|---|---|
|
||||||
|
| Application | Root composition/provider |
|
||||||
|
| Route branch | Route layout composition |
|
||||||
|
| Page | Page composition/provider |
|
||||||
|
| Component flow | Nested composition module |
|
||||||
|
| Request | Server composition/request builder |
|
||||||
|
| Test | Test setup/wrapper |
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-005 - ОБЯЗАН.** Scope owner должен определить количество instances и duration каждого mutable runtime или resource.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-006 - ЗАПРЕЩЕНО.** Module-level singleton не может использоваться как случайная замена application scope.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-007 - МОЖЕТ.** Application singleton допустим только при явном application ownership и отсутствии request-, identity- и user-specific data.
|
||||||
|
|
||||||
|
## Activation и cleanup
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-009 - ОБЯЗАН.** Повторный mount/unmount, включая development Strict Mode, не должен оставлять duplicate subscription или abandoned resource.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-010 - СЛЕДУЕТ.** `start` и cleanup следует проектировать idempotent либо явно защищать от повторного вызова.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-018 - ОБЯЗАН.** Если activation составного resource set завершилась ошибкой, scope owner должен освободить уже успешно запущенную часть в обратном порядке.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-019 - ОБЯЗАН.** Ошибка cleanup должна быть наблюдаемой и не должна препятствовать попытке освободить остальные resources scope.
|
||||||
|
|
||||||
|
## Events и sockets
|
||||||
|
|
||||||
|
Product event обрабатывается владельцем product semantics; socket остаётся technical transport.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-011 - ЗАПРЕЩЕНО.** Framework component не может открывать product socket напрямую при render или module import.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-012 - ОБЯЗАН.** Invalid event и connection failure должны преобразовываться в product state/outcome либо technical telemetry согласно их semantics; callback error нельзя терять через unobserved throw.
|
||||||
|
|
||||||
|
## Revalidation events
|
||||||
|
|
||||||
|
Event может содержать product update или только сообщать об устаревании данных.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-014 - ОБЯЗАН.** Invalidation intent должен выражаться product language и не требовать import конкретной query library в public product contract.
|
||||||
|
|
||||||
|
## Server runtime
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-015 - ОБЯЗАН.** User-specific server runtime создаётся в request scope.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-016 - ЗАПРЕЩЕНО.** Process singleton не может захватывать request headers, cookies, credentials, AbortSignal или user-specific cache.
|
||||||
|
|
||||||
|
**SLM-BASE-LIFE-017 - ОБЯЗАН.** Request cancellation должна передаваться external operations, если runtime и используемая integration поддерживают cancellation.
|
||||||
|
|
||||||
|
Overlay может вводить дополнительные lifecycle boundaries только внутри собственного delta.
|
||||||
@@ -27,15 +27,15 @@ Segment группирует внутренние файлы module по уст
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
**SLM-SEG-001 - МОЖЕТ.** Module может использовать любые необходимые segments и не обязан создавать остальные.
|
**SLM-BASE-SEG-001 - МОЖЕТ.** Module может использовать любые необходимые segments и не обязан создавать остальные.
|
||||||
|
|
||||||
**SLM-SEG-002 - ЗАПРЕЩЕНО.** Нельзя создавать полный симметричный набор segments как scaffold без реального содержимого.
|
**SLM-BASE-SEG-002 - ЗАПРЕЩЕНО.** Нельзя создавать полный симметричный набор segments как scaffold без реального содержимого.
|
||||||
|
|
||||||
**SLM-SEG-003 - ОБЯЗАН.** Если файл помещён в segment, роль segment должна соответствовать фактической роли файла, а не только его расширению или имени. Файлы могут оставаться в корне небольшого module.
|
**SLM-BASE-SEG-003 - ОБЯЗАН.** Если файл помещён в segment, роль segment должна соответствовать фактической роли файла, а не только его расширению или имени. Файлы могут оставаться в корне небольшого module.
|
||||||
|
|
||||||
**SLM-SEG-004 - ЗАПРЕЩЕНО.** Segment не имеет внешнего public API независимо от module owner.
|
**SLM-BASE-SEG-004 - ЗАПРЕЩЕНО.** Segment не имеет внешнего public API независимо от module owner.
|
||||||
|
|
||||||
Запрет deep import в segment другого module определяется base-правилом `SLM-API-002`.
|
Запрет deep import в segment другого module определяется base-правилом `SLM-BASE-API-002`.
|
||||||
|
|
||||||
## UI и Parts
|
## UI и Parts
|
||||||
|
|
||||||
@@ -43,11 +43,11 @@ Segment группирует внутренние файлы module по уст
|
|||||||
|
|
||||||
`parts/` содержит nested modules с собственной внутренней структурой и локальным public boundary.
|
`parts/` содержит nested modules с собственной внутренней структурой и локальным public boundary.
|
||||||
|
|
||||||
**SLM-SEG-006 - ОБЯЗАН.** Сущность с самостоятельной ответственностью, внешними архитектурными dependencies или nested modules должна размещаться в `parts`, а не маскироваться как плоский component. Локальные presentation hooks/state сами по себе не требуют `parts`.
|
**SLM-BASE-SEG-006 - ОБЯЗАН.** Сущность с самостоятельной ответственностью, внешними архитектурными dependencies или nested modules должна размещаться в `parts`, а не маскироваться как плоский component. Локальные presentation hooks/state сами по себе не требуют `parts`.
|
||||||
|
|
||||||
## Hooks
|
## Hooks
|
||||||
|
|
||||||
**SLM-SEG-007 - ОБЯЗАН.** Hook принадлежит тому module, чью ответственность и runtime он выражает.
|
**SLM-BASE-SEG-007 - ОБЯЗАН.** Hook принадлежит тому module, чью ответственность и runtime он выражает.
|
||||||
|
|
||||||
Примеры:
|
Примеры:
|
||||||
|
|
||||||
70
docs/ru/specification/state-and-data.md
Normal file
70
docs/ru/specification/state-and-data.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: State и data
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# State и Data
|
||||||
|
|
||||||
|
SLM рассматривает данные и состояние через одного владельца semantics, даже если runtime использует несколько caches и projections.
|
||||||
|
|
||||||
|
## Ownership matrix
|
||||||
|
|
||||||
|
| Вид | Владелец |
|
||||||
|
|---|---|
|
||||||
|
| Product model и transitions | Product owner |
|
||||||
|
| Product source integration | Product owner; technical mechanism остаётся в infra |
|
||||||
|
| Framework projection product data | Public surface владельца product data |
|
||||||
|
| Page-local presentation state | Composition |
|
||||||
|
| Component-local interaction | Владеющий component/module |
|
||||||
|
| Technical connection/cache state | Infra или runtime-specific owner |
|
||||||
|
| Request context | Server/framework scope |
|
||||||
|
| Universal UI state | Владеющий UI module |
|
||||||
|
|
||||||
|
## Product gateway
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-001 - ОБЯЗАН.** Consumer должен получать product data через public boundary владеющего module.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-002 - ЗАПРЕЩЕНО.** Composition, UI или app не могут маппить transport DTO в параллельную product model, если модель уже имеет другого owner.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-003 - ОБЯЗАН.** Product owner владеет normalization, validation и semantics отсутствия данных.
|
||||||
|
|
||||||
|
## Product state
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-004 - ОБЯЗАН.** Product state model и допустимые transitions должны определяться product owner независимо от concrete state manager.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-005 - ЗАПРЕЩЕНО.** Concrete mutable store implementation не может становиться public product contract без явно объявленного владельцем стабильного store access API.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-006 - ОБЯЗАН.** Mutable product instance должен быть привязан к явному lifecycle scope.
|
||||||
|
|
||||||
|
## Query cache
|
||||||
|
|
||||||
|
Framework или technical query cache может хранить projection результата product query.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-007 - ОБЯЗАН.** Query/cache consumer за пределами product owner должен использовать public boundary владельца и не может обходить его прямым вызовом private integration или SDK.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-008 - ЗАПРЕЩЕНО.** Query cache не может объявлять собственную product model, error taxonomy или fallback policy.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-009 - ОБЯЗАН.** User/session-scoped cache keys и invalidation должны изолировать данные разных identities и scopes без использования secret как публичного key contract.
|
||||||
|
|
||||||
|
Эта draft-версия не предписывает единственное физическое место QueryClient/SWR cache. Конкретная модель оценивается по правилам public boundary владельца, lifecycle и identity isolation.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-015 - ОБЯЗАН.** Cache instance должен иметь явного creator и scope owner в composition или runtime setup.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-016 - ОБЯЗАН.** Shared framework cache должен передаваться consumers через framework-supported runtime boundary, а не через import app-specific mutable singleton.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-017 - ОБЯЗАН.** Scope owner должен очищать или изолировать private cache при смене identity и завершении соответствующего scope.
|
||||||
|
|
||||||
|
## Presentation state
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-010 - МОЖЕТ.** Composition или component может использовать concrete state manager для локального presentation state.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-011 - ЗАПРЕЩЕНО.** Presentation store не должен копировать canonical product state как второй source of truth.
|
||||||
|
|
||||||
|
## Serializable boundaries
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-012 - ОБЯЗАН.** Через server/client boundary передаются только serializable product-owned data без functions, stores, clients, Context и resources.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-013 - ЗАПРЕЩЕНО.** Secrets, access tokens и request credentials не должны включаться в client bootstrap snapshot.
|
||||||
|
|
||||||
|
**SLM-BASE-DATA-014 - ОБЯЗАН.** Server и client initial snapshots должны быть согласованы, если framework выполняет hydration одного UI state.
|
||||||
58
docs/ru/specification/testing-and-conformance.md
Normal file
58
docs/ru/specification/testing-and-conformance.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
title: Тестирование и соответствие
|
||||||
|
status: draft
|
||||||
|
normative: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Тестирование и Соответствие
|
||||||
|
|
||||||
|
Тесты проверяют public boundaries и runtime risks каждого owner. Base SLM не требует создавать неиспользуемые архитектурные конструкции ради тестовой формы.
|
||||||
|
|
||||||
|
## Risk-based tests
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-018 - ОБЯЗАН.** Tests изменённого module должны покрывать применимые риски его public behavior, data boundaries и lifecycle.
|
||||||
|
|
||||||
|
Типичные риски:
|
||||||
|
|
||||||
|
- public behavior;
|
||||||
|
- malformed external data;
|
||||||
|
- rejected dependencies;
|
||||||
|
- state transitions;
|
||||||
|
- lifecycle activation и cleanup;
|
||||||
|
- request и identity isolation;
|
||||||
|
- client/server boundary;
|
||||||
|
- отсутствие import-time I/O.
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-008 - ОБЯЗАН.** Client/server import boundary должна проверяться инструментом, понимающим реальный framework module graph, если application имеет раздельные environment entries. DOM unit test не заменяет production build probe.
|
||||||
|
|
||||||
|
Mode-specific test suites принадлежат overlay, который вводит соответствующие конструкции.
|
||||||
|
|
||||||
|
## Architecture conformance
|
||||||
|
|
||||||
|
Типичные mechanically enforceable checks:
|
||||||
|
|
||||||
|
- направление imports;
|
||||||
|
- deep imports;
|
||||||
|
- public entrypoints;
|
||||||
|
- runtime cycles;
|
||||||
|
- заявленный overlay и его rule set;
|
||||||
|
- unique rule IDs документации;
|
||||||
|
- generated artifacts, если они используются.
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-011 - ЗАПРЕЩЕНО.** Документированное правило не считается mechanically enforced, если repository tooling его фактически не проверяет.
|
||||||
|
|
||||||
|
## Единица соответствия
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-014 - ОБЯЗАН.** Application соответствует base SLM, если выполняет все base-правила. Соответствие заявленному overlay оценивается как base-правила с учётом точного scope каждой замены плюс полный rule set выбранного overlay.
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-015 - ОБЯЗАН.** Изменение соответствует заявленной архитектуре, если новые и изменённые modules не создают новых нарушений применимых base-правил или правил выбранного overlay и проходят существующие checks.
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-016 - ОБЯЗАН.** Отступление от правила `СЛЕДУЕТ` должно быть зафиксировано в архитектурном review или принятом decision с указанием причины и scope.
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-017 - ОБЯЗАН.** Manual conformance и mechanical enforcement должны различаться явно; отсутствие автоматической проверки не отменяет применимое нормативное правило.
|
||||||
|
|
||||||
|
## Completion gate
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-012 - ОБЯЗАН.** Изменение считается завершённым только после выполнения ближайших tests, typecheck, lint, build и architecture checks, существующих в repository.
|
||||||
|
|
||||||
|
**SLM-BASE-TEST-013 - ОБЯЗАН.** Невыполненная проверка и остаточный риск должны быть явно указаны в результате работы.
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# SLM Design 2.0 Draft
|
|
||||||
|
|
||||||
`docs2/` содержит черновик новой спецификации SLM Design.
|
|
||||||
|
|
||||||
Текущая документация в `docs/` остаётся действующим источником истины до отдельного решения о принятии новой спецификации. Skill и его generated reference пока не используют `docs2/`.
|
|
||||||
|
|
||||||
## Точка входа
|
|
||||||
|
|
||||||
[SLM Design Specification](./specification/index.md)
|
|
||||||
|
|
||||||
Specification определяет base SLM и два независимых [architecture modes](./specification/architecture-modes.md): `SLM Advanced` и `SLM Pro`. Каждый mode является отдельным overlay непосредственно над base SLM.
|
|
||||||
|
|
||||||
## Границы текущего этапа
|
|
||||||
|
|
||||||
На этом этапе в `docs2/` размещается только нормативная спецификация. Учебные материалы, руководства, примеры, справочники и agent skill будут проектироваться после стабилизации правил.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
---
|
|
||||||
title: Основные инварианты
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Основные Инварианты
|
|
||||||
|
|
||||||
SLM Design организует frontend-приложение по владельцам ответственности. Архитектурная единица определяется не типом файла, а тем, кто владеет моделью, поведением, данными, runtime и lifecycle.
|
|
||||||
|
|
||||||
## Ответственность до размещения
|
|
||||||
|
|
||||||
**SLM-FND-001 - ОБЯЗАН.** Перед размещением кода необходимо определить его владельца, public boundary, runtime dependencies и lifecycle scope.
|
|
||||||
|
|
||||||
**SLM-FND-002 - СЛЕДУЕТ.** Код следует размещать в минимальном scope, который полностью владеет его ответственностью.
|
|
||||||
|
|
||||||
**SLM-FND-003 - ЗАПРЕЩЕНО.** Нельзя переносить код в общий слой или общий package только на основании предполагаемого будущего переиспользования.
|
|
||||||
|
|
||||||
## Путь продуктовых данных
|
|
||||||
|
|
||||||
Product data проходят через public boundary текущего владельца согласно [SLM-DATA-001](./state-and-data.md#product-gateway). Внешний сервис может оставаться физическим источником данных, но transport contract не становится product model автоматически.
|
|
||||||
|
|
||||||
## Явные зависимости
|
|
||||||
|
|
||||||
**SLM-FND-007 - ОБЯЗАН.** Runtime capabilities должны поступать владельцу поведения через разрешённые imports, явные arguments или contracts, а не через скрытый service locator или global mutable state.
|
|
||||||
|
|
||||||
**SLM-FND-008 - ЗАПРЕЩЕНО.** Type cast, barrel, alias, dynamic import или helper в `shared` не могут использоваться для обхода применимой архитектурной границы.
|
|
||||||
|
|
||||||
## Public API
|
|
||||||
|
|
||||||
Межмодульное взаимодействие и deep imports регулируются [SLM-API-001 - SLM-API-005](./public-api-and-imports.md#общие-правила).
|
|
||||||
|
|
||||||
## Scope и lifecycle
|
|
||||||
|
|
||||||
Создание, scope, activation и cleanup применимых runtimes и resources определены в [Runtime и lifecycle](./runtime-and-lifecycle.md).
|
|
||||||
|
|
||||||
## Overlays
|
|
||||||
|
|
||||||
Base SLM не вводит дополнительные архитектурные слои и специализированные runtime contracts. Каждый overlay самостоятельно определяет свои добавления и замены base-правил.
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
title: Слой Infra
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Слой Infra
|
|
||||||
|
|
||||||
`infra` содержит technical capabilities приложения, не определяющие product model и scenarios.
|
|
||||||
|
|
||||||
## Примеры modules
|
|
||||||
|
|
||||||
```text
|
|
||||||
infra/
|
|
||||||
├── http/
|
|
||||||
├── backend-api/
|
|
||||||
├── realtime/
|
|
||||||
├── analytics/
|
|
||||||
├── logger/
|
|
||||||
├── app-config/
|
|
||||||
├── storage/
|
|
||||||
├── i18n/
|
|
||||||
└── theme/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Правила
|
|
||||||
|
|
||||||
**SLM-INF-001 - ОБЯЗАН.** Infra module должен описывать technical capability, а не product semantics или scenario.
|
|
||||||
|
|
||||||
**SLM-INF-002 - МОЖЕТ.** Infra module может импортировать public API другого infra module и `shared`.
|
|
||||||
|
|
||||||
Запрет infra импортировать `compositions` или `app` определяется base-правилом `SLM-ARCH-003`.
|
|
||||||
|
|
||||||
**SLM-INF-004 - ЗАПРЕЩЕНО.** Infra не может владеть product wiring, собирать application graph или предоставлять generic product service locator.
|
|
||||||
|
|
||||||
**SLM-INF-005 - ЗАПРЕЩЕНО.** Infra не создаёт product errors, product fallback и product model из transport DTO.
|
|
||||||
|
|
||||||
**SLM-INF-006 - МОЖЕТ.** Infra может экспортировать technical client, transport, event source, storage primitive или platform wrapper через собственный public API.
|
|
||||||
|
|
||||||
**SLM-INF-007 - ОБЯЗАН.** Generated SDK и transport details должны оставаться внутри technical или private integration boundary владельца и не становиться частью public product contract.
|
|
||||||
|
|
||||||
## Product integration
|
|
||||||
|
|
||||||
Infra знает technical mechanism:
|
|
||||||
|
|
||||||
```text
|
|
||||||
HTTP client
|
|
||||||
WebSocket transport
|
|
||||||
local storage primitive
|
|
||||||
analytics SDK
|
|
||||||
```
|
|
||||||
|
|
||||||
Product owner определяет semantics использования capability; infra предоставляет механизм через public API. Один infra module может использоваться несколькими product owners без знания их semantics.
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
title: Слой Shared
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Слой Shared
|
|
||||||
|
|
||||||
`shared` является детерминированным фундаментом приложения и не знает о SLM-модулях верхних слоёв.
|
|
||||||
|
|
||||||
## Допустимое содержимое
|
|
||||||
|
|
||||||
- pure utilities;
|
|
||||||
- value predicates;
|
|
||||||
- product-agnostic types;
|
|
||||||
- styling foundation и tokens;
|
|
||||||
- static resources;
|
|
||||||
- compile-time constants без product ownership;
|
|
||||||
- deterministic formatting primitives.
|
|
||||||
|
|
||||||
## Правила
|
|
||||||
|
|
||||||
**SLM-SHR-001 - ОБЯЗАН.** Результат shared utility должен определяться явными аргументами и не зависеть от скрытого runtime environment.
|
|
||||||
|
|
||||||
**SLM-SHR-002 - ЗАПРЕЩЕНО.** `shared` не может импортировать `app`, `compositions`, `infra` или `ui`.
|
|
||||||
|
|
||||||
**SLM-SHR-003 - ЗАПРЕЩЕНО.** `shared` не может владеть product types, product rules, runtime state, I/O, storage access или event subscriptions.
|
|
||||||
|
|
||||||
**SLM-SHR-004 - ЗАПРЕЩЕНО.** Нельзя переносить product helper, DTO, integration contract или product config в `shared` для обхода import boundary.
|
|
||||||
|
|
||||||
**SLM-SHR-005 - СЛЕДУЕТ.** Код следует поднимать в `shared` только при подтверждённой product-agnostic semantics, а не из-за повторения нескольких строк.
|
|
||||||
|
|
||||||
## Отличие от других слоёв
|
|
||||||
|
|
||||||
| Код | Владелец |
|
|
||||||
|---|---|
|
|
||||||
| Email validator с product rules | Владеющий product module |
|
|
||||||
| Generic string trim utility | `shared` |
|
|
||||||
| Browser storage wrapper | `infra` |
|
|
||||||
| Product storage integration | Product owner; storage primitive - `infra` |
|
|
||||||
| UI spacing tokens | `shared` |
|
|
||||||
| Button consuming spacing tokens | `ui` |
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
---
|
|
||||||
title: Слой UI
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Слой UI
|
|
||||||
|
|
||||||
`ui` содержит reusable presentation modules без product scenario и product ownership.
|
|
||||||
|
|
||||||
## Примеры
|
|
||||||
|
|
||||||
```text
|
|
||||||
ui/
|
|
||||||
├── button/
|
|
||||||
├── input/
|
|
||||||
├── icon/
|
|
||||||
├── modal/
|
|
||||||
├── carousel/
|
|
||||||
├── tabs/
|
|
||||||
└── tooltip/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Правила
|
|
||||||
|
|
||||||
**SLM-UI-001 - ОБЯЗАН.** UI module должен быть применим без product-specific knowledge.
|
|
||||||
|
|
||||||
**SLM-UI-002 - ЗАПРЕЩЕНО.** UI module не может импортировать `compositions`, `app` или product-specific infra.
|
|
||||||
|
|
||||||
**SLM-UI-003 - МОЖЕТ.** UI module может импортировать public API других UI modules и `shared`.
|
|
||||||
|
|
||||||
**SLM-UI-004 - ЗАПРЕЩЕНО.** UI module не выбирает product data source, не вызывает product scenario и не владеет multi-module behavior.
|
|
||||||
|
|
||||||
**SLM-UI-005 - МОЖЕТ.** UI module может владеть локальным interaction state, необходимым только для собственной presentation mechanics.
|
|
||||||
|
|
||||||
**SLM-UI-006 - ОБЯЗАН.** Компонент с product semantics должен принадлежать владеющему product module, а не `ui`.
|
|
||||||
|
|
||||||
## Классификация
|
|
||||||
|
|
||||||
| Сущность | Владелец |
|
|
||||||
|---|---|
|
|
||||||
| `Button`, `Input`, `Modal` | `ui` |
|
|
||||||
| `LoginForm` одной auth responsibility | Владеющий product module |
|
|
||||||
| Application header | `compositions` |
|
|
||||||
| Generic date picker | `ui` |
|
|
||||||
| Medication schedule | Владеющий product module согласно ownership |
|
|
||||||
|
|
||||||
Универсальность определяется отсутствием product knowledge, а не количеством текущих consumers.
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
---
|
|
||||||
title: Модули и группы
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Модули и Группы
|
|
||||||
|
|
||||||
## Module
|
|
||||||
|
|
||||||
Module является минимальным самостоятельным владельцем ответственности и предоставляет public boundary внешнему коду.
|
|
||||||
|
|
||||||
**SLM-MOD-001 - ОБЯЗАН.** Module должен иметь одну сформулированную ответственность и одного архитектурного owner.
|
|
||||||
|
|
||||||
**SLM-MOD-002 - ОБЯЗАН.** Внешний consumer взаимодействует с module только через его public API.
|
|
||||||
|
|
||||||
**SLM-MOD-003 - СЛЕДУЕТ.** Module следует ограничивать только теми внутренними parts и segments, которые необходимы текущей ответственности.
|
|
||||||
|
|
||||||
Типичные modules:
|
|
||||||
|
|
||||||
- page, layout, screen или widget в `compositions`;
|
|
||||||
- technical service в `infra`;
|
|
||||||
- reusable UI module в `ui`.
|
|
||||||
|
|
||||||
`app` содержит framework entries и не обязан организовываться как SLM modules. `shared` может содержать небольшие public units, но не runtime modules.
|
|
||||||
|
|
||||||
## Group
|
|
||||||
|
|
||||||
Group классифицирует modules и другие groups, но не владеет поведением.
|
|
||||||
|
|
||||||
**SLM-MOD-004 - ЗАПРЕЩЕНО.** Group не может иметь `index.ts`, public API, state, runtime, dependencies или assembly.
|
|
||||||
|
|
||||||
**SLM-MOD-005 - ЗАПРЕЩЕНО.** Внешний код не может импортировать group path.
|
|
||||||
|
|
||||||
**SLM-MOD-006 - МОЖЕТ.** Group может содержать другие groups и конечные modules.
|
|
||||||
|
|
||||||
```text
|
|
||||||
compositions/
|
|
||||||
└── pages/ # group
|
|
||||||
├── home/ # composition module
|
|
||||||
└── profile/ # composition module
|
|
||||||
```
|
|
||||||
|
|
||||||
## Component
|
|
||||||
|
|
||||||
Component является presentation unit внутри module и не считается самостоятельным архитектурным owner.
|
|
||||||
|
|
||||||
**SLM-MOD-008 - ЗАПРЕЩЕНО.** Component не может самостоятельно выбирать application-level product source, выполнять module wiring или оркестрировать несколько самостоятельных modules.
|
|
||||||
|
|
||||||
**SLM-MOD-009 - МОЖЕТ.** Component может владеть локальной presentation mechanics и рендерить другие components, разрешённые слоем владельца.
|
|
||||||
|
|
||||||
**SLM-MOD-010 - ОБЯЗАН.** Presentation unit с самостоятельной ответственностью, внешними архитектурными dependencies или внутренней modular structure должна оформляться как module или nested module. Сам факт локального hook/state не делает component модулем.
|
|
||||||
|
|
||||||
## Nested module
|
|
||||||
|
|
||||||
Самостоятельная часть родительского module может быть оформлена nested module, если имеет собственную ответственность и public boundary только внутри родителя.
|
|
||||||
|
|
||||||
```text
|
|
||||||
compositions/pages/home/
|
|
||||||
└── parts/
|
|
||||||
└── hero-section/
|
|
||||||
├── hero-section.tsx
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-MOD-011 - ЗАПРЕЩЕНО.** Nested module не может использоваться для сокрытия ответственности, которой фактически владеет другой module или layer.
|
|
||||||
|
|
||||||
## Scope evolution
|
|
||||||
|
|
||||||
**SLM-MOD-012 - СЛЕДУЕТ.** Код следует поднимать из локального owner в более широкий module только после появления реального совместного consumer или общей ответственности.
|
|
||||||
|
|
||||||
**SLM-MOD-013 - ЗАПРЕЩЕНО.** Физическое повторение само по себе не доказывает общий ownership.
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
title: Монорепозитории
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Монорепозитории
|
|
||||||
|
|
||||||
SLM применяется внутри границы каждого frontend-приложения. Workspace packages имеют собственные public boundaries и ownership.
|
|
||||||
|
|
||||||
## Application boundary
|
|
||||||
|
|
||||||
```text
|
|
||||||
apps/
|
|
||||||
└── web/
|
|
||||||
└── src/
|
|
||||||
├── app/
|
|
||||||
├── compositions/
|
|
||||||
├── infra/
|
|
||||||
├── ui/
|
|
||||||
└── shared/
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-MONO-001 - ОБЯЗАН.** Каждое приложение должно самостоятельно определять свои application compositions, product ownership и runtime wiring.
|
|
||||||
|
|
||||||
**SLM-MONO-002 - ЗАПРЕЩЕНО.** Workspace package не может импортировать код из `apps/*`.
|
|
||||||
|
|
||||||
**SLM-MONO-003 - ЗАПРЕЩЕНО.** Одно приложение не может deep-import исходники другого приложения вместо общего package contract.
|
|
||||||
|
|
||||||
## Package boundary
|
|
||||||
|
|
||||||
**SLM-MONO-004 - ОБЯЗАН.** Package должен иметь самостоятельного owner, public exports и подтверждённую reuse/ownership semantics.
|
|
||||||
|
|
||||||
**SLM-MONO-005 - ЗАПРЕЩЕНО.** Нельзя создавать package только для обхода layer direction, public API или иной объявленной dependency boundary.
|
|
||||||
|
|
||||||
**SLM-MONO-006 - ОБЯЗАН.** Consumers импортируют package через объявленный package export, а не через filesystem path к internal source.
|
|
||||||
|
|
||||||
## Типичные packages
|
|
||||||
|
|
||||||
Допустимыми кандидатами являются:
|
|
||||||
|
|
||||||
- product-agnostic UI kit;
|
|
||||||
- technical infra client;
|
|
||||||
- deterministic shared foundation;
|
|
||||||
- schema/codegen/tooling package;
|
|
||||||
- configuration package без application-specific wiring.
|
|
||||||
|
|
||||||
Base SLM не присваивает package дополнительный архитектурный статус автоматически.
|
|
||||||
|
|
||||||
## Dependency direction
|
|
||||||
|
|
||||||
**SLM-MONO-009 - ОБЯЗАН.** Package dependency graph должен оставаться ацикличным и соответствовать заявленной ответственности packages.
|
|
||||||
|
|
||||||
**SLM-MONO-010 - ЗАПРЕЩЕНО.** Shared package не может импортировать application composition или app-specific infra package.
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
title: Public API и импорты
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Public API и Импорты
|
|
||||||
|
|
||||||
Public API ограничивает знание consumers о внутренней структуре module. Точная форма entrypoint определяется владельцем и не требует обязательного `index.ts`.
|
|
||||||
|
|
||||||
## Общие правила
|
|
||||||
|
|
||||||
**SLM-API-001 - ОБЯЗАН.** Межмодульный import должен использовать объявленный public entrypoint импортируемого module.
|
|
||||||
|
|
||||||
**SLM-API-002 - ЗАПРЕЩЕНО.** Deep imports во внутренние segments, files и иные private paths другого module запрещены.
|
|
||||||
|
|
||||||
**SLM-API-003 - ОБЯЗАН.** Каждый runtime export должен иметь реального consumer за пределами владеющего entrypoint и стабильную ответственность.
|
|
||||||
|
|
||||||
**SLM-API-004 - ЗАПРЕЩЕНО.** Public API не может случайно раскрывать implementation unit, который владелец считает private или lifecycle которого не является частью public contract.
|
|
||||||
|
|
||||||
**SLM-API-005 - ОБЯЗАН.** Alias или package subpath должен физически разрешаться TypeScript, tests и production build.
|
|
||||||
|
|
||||||
**SLM-API-009 - МОЖЕТ.** Public entrypoint может быть root `index.ts`, отдельным named entry, package export или другим явно объявленным path.
|
|
||||||
|
|
||||||
**SLM-API-010 - ОБЯЗАН.** Public и private paths module должны быть различимы consumers и repository tooling.
|
|
||||||
|
|
||||||
## Layer matrix
|
|
||||||
|
|
||||||
| Importer | Runtime imports |
|
|
||||||
|---|---|
|
|
||||||
| `app` | Public composition entries, shared static/global resources |
|
|
||||||
| `compositions` | Compositions, infra, ui, shared |
|
|
||||||
| `infra` | Infra, shared |
|
|
||||||
| `ui` | UI, shared |
|
|
||||||
| `shared` | External pure libraries only |
|
|
||||||
|
|
||||||
## Type-only imports
|
|
||||||
|
|
||||||
**SLM-API-006 - МОЖЕТ.** `import type` может использоваться для разрешённого contract dependency без создания runtime edge.
|
|
||||||
|
|
||||||
**SLM-API-007 - ЗАПРЕЩЕНО.** Type-only import не разрешает перенос ownership, импорт private concrete runtime type или обход layer boundary.
|
|
||||||
|
|
||||||
## Groups
|
|
||||||
|
|
||||||
Отсутствие public entrypoint у group определяется base-правилом `SLM-MOD-004`.
|
|
||||||
|
|
||||||
**SLM-API-015 - ОБЯЗАН.** Composition public API экспортирует только entry components, access APIs, types и contracts, необходимые внешним composition consumers.
|
|
||||||
|
|
||||||
## Cycles
|
|
||||||
|
|
||||||
**SLM-API-016 - ЗАПРЕЩЕНО.** Runtime import cycle между modules запрещён независимо от того, способен ли bundler его выполнить.
|
|
||||||
|
|
||||||
**SLM-API-017 - ЗАПРЕЩЕНО.** Barrel не должен создавать скрытый cycle между ready composition и access API её children.
|
|
||||||
|
|
||||||
Дополнительные entrypoints и import restrictions принадлежат overlay, который их вводит.
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
---
|
|
||||||
title: Runtime и lifecycle
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Runtime и Lifecycle
|
|
||||||
|
|
||||||
Lifecycle является архитектурной частью любого mutable runtime, subscription и external resource. Эти правила не требуют создавать отдельный runtime или factory, если у module нет соответствующего состояния или resources.
|
|
||||||
|
|
||||||
## Definition, creation и activation
|
|
||||||
|
|
||||||
Для module с создаваемым runtime применима модель:
|
|
||||||
|
|
||||||
```text
|
|
||||||
definition
|
|
||||||
-> module объявляет creator
|
|
||||||
|
|
||||||
creation
|
|
||||||
-> creator создаёт instance без external effects
|
|
||||||
|
|
||||||
activation
|
|
||||||
-> scope owner запускает resources и получает cleanup
|
|
||||||
```
|
|
||||||
|
|
||||||
**SLM-LIFE-001 - ЗАПРЕЩЕНО.** Module import не должен выполнять product I/O, открывать connection или регистрировать global listener.
|
|
||||||
|
|
||||||
**SLM-LIFE-002 - ОБЯЗАН.** Если module предоставляет factory или runtime creator, creation должна быть side-effect free относительно external resources.
|
|
||||||
|
|
||||||
**SLM-LIFE-003 - ОБЯЗАН.** Subscription, socket, timer и listener запускаются явной operation владельца scope.
|
|
||||||
|
|
||||||
**SLM-LIFE-004 - ОБЯЗАН.** Каждый запущенный resource должен иметь cleanup или dispose contract.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
| Scope | Примеры владельца |
|
|
||||||
|---|---|
|
|
||||||
| Application | Root composition/provider |
|
|
||||||
| Route branch | Route layout composition |
|
|
||||||
| Page | Page composition/provider |
|
|
||||||
| Component flow | Nested composition module |
|
|
||||||
| Request | Server composition/request builder |
|
|
||||||
| Test | Test setup/wrapper |
|
|
||||||
|
|
||||||
**SLM-LIFE-005 - ОБЯЗАН.** Scope owner должен определить количество instances и duration каждого mutable runtime или resource.
|
|
||||||
|
|
||||||
**SLM-LIFE-006 - ЗАПРЕЩЕНО.** Module-level singleton не может использоваться как случайная замена application scope.
|
|
||||||
|
|
||||||
**SLM-LIFE-007 - МОЖЕТ.** Application singleton допустим только при явном application ownership и отсутствии request-, identity- и user-specific data.
|
|
||||||
|
|
||||||
## Activation и cleanup
|
|
||||||
|
|
||||||
**SLM-LIFE-009 - ОБЯЗАН.** Повторный mount/unmount, включая development Strict Mode, не должен оставлять duplicate subscription или abandoned resource.
|
|
||||||
|
|
||||||
**SLM-LIFE-010 - СЛЕДУЕТ.** `start` и cleanup следует проектировать idempotent либо явно защищать от повторного вызова.
|
|
||||||
|
|
||||||
**SLM-LIFE-018 - ОБЯЗАН.** Если activation составного resource set завершилась ошибкой, scope owner должен освободить уже успешно запущенную часть в обратном порядке.
|
|
||||||
|
|
||||||
**SLM-LIFE-019 - ОБЯЗАН.** Ошибка cleanup должна быть наблюдаемой и не должна препятствовать попытке освободить остальные resources scope.
|
|
||||||
|
|
||||||
## Events и sockets
|
|
||||||
|
|
||||||
Product event обрабатывается владельцем product semantics; socket остаётся technical transport.
|
|
||||||
|
|
||||||
**SLM-LIFE-011 - ЗАПРЕЩЕНО.** Framework component не может открывать product socket напрямую при render или module import.
|
|
||||||
|
|
||||||
**SLM-LIFE-012 - ОБЯЗАН.** Invalid event и connection failure должны преобразовываться в product state/outcome либо technical telemetry согласно их semantics; callback error нельзя терять через unobserved throw.
|
|
||||||
|
|
||||||
## Revalidation events
|
|
||||||
|
|
||||||
Event может содержать product update или только сообщать об устаревании данных.
|
|
||||||
|
|
||||||
**SLM-LIFE-014 - ОБЯЗАН.** Invalidation intent должен выражаться product language и не требовать import конкретной query library в public product contract.
|
|
||||||
|
|
||||||
## Server runtime
|
|
||||||
|
|
||||||
**SLM-LIFE-015 - ОБЯЗАН.** User-specific server runtime создаётся в request scope.
|
|
||||||
|
|
||||||
**SLM-LIFE-016 - ЗАПРЕЩЕНО.** Process singleton не может захватывать request headers, cookies, credentials, AbortSignal или user-specific cache.
|
|
||||||
|
|
||||||
**SLM-LIFE-017 - ОБЯЗАН.** Request cancellation должна передаваться external operations, если runtime и используемая integration поддерживают cancellation.
|
|
||||||
|
|
||||||
Overlay может вводить дополнительные lifecycle boundaries только внутри собственного delta.
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
---
|
|
||||||
title: State и data
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# State и Data
|
|
||||||
|
|
||||||
SLM рассматривает данные и состояние через одного владельца semantics, даже если runtime использует несколько caches и projections.
|
|
||||||
|
|
||||||
## Ownership matrix
|
|
||||||
|
|
||||||
| Вид | Владелец |
|
|
||||||
|---|---|
|
|
||||||
| Product model и transitions | Product owner |
|
|
||||||
| Product source integration | Product owner; technical mechanism остаётся в infra |
|
|
||||||
| Framework projection product data | Public surface владельца product data |
|
|
||||||
| Page-local presentation state | Composition |
|
|
||||||
| Component-local interaction | Владеющий component/module |
|
|
||||||
| Technical connection/cache state | Infra или runtime-specific owner |
|
|
||||||
| Request context | Server/framework scope |
|
|
||||||
| Universal UI state | Владеющий UI module |
|
|
||||||
|
|
||||||
## Product gateway
|
|
||||||
|
|
||||||
**SLM-DATA-001 - ОБЯЗАН.** Consumer должен получать product data через public boundary владеющего module.
|
|
||||||
|
|
||||||
**SLM-DATA-002 - ЗАПРЕЩЕНО.** Composition, UI или app не могут маппить transport DTO в параллельную product model, если модель уже имеет другого owner.
|
|
||||||
|
|
||||||
**SLM-DATA-003 - ОБЯЗАН.** Product owner владеет normalization, validation и semantics отсутствия данных.
|
|
||||||
|
|
||||||
## Product state
|
|
||||||
|
|
||||||
**SLM-DATA-004 - ОБЯЗАН.** Product state model и допустимые transitions должны определяться product owner независимо от concrete state manager.
|
|
||||||
|
|
||||||
**SLM-DATA-005 - ЗАПРЕЩЕНО.** Concrete mutable store implementation не может становиться public product contract без явно объявленного владельцем стабильного store access API.
|
|
||||||
|
|
||||||
**SLM-DATA-006 - ОБЯЗАН.** Mutable product instance должен быть привязан к явному lifecycle scope.
|
|
||||||
|
|
||||||
## Query cache
|
|
||||||
|
|
||||||
Framework или technical query cache может хранить projection результата product query.
|
|
||||||
|
|
||||||
**SLM-DATA-007 - ОБЯЗАН.** Query/cache consumer за пределами product owner должен использовать public boundary владельца и не может обходить его прямым вызовом private integration или SDK.
|
|
||||||
|
|
||||||
**SLM-DATA-008 - ЗАПРЕЩЕНО.** Query cache не может объявлять собственную product model, error taxonomy или fallback policy.
|
|
||||||
|
|
||||||
**SLM-DATA-009 - ОБЯЗАН.** User/session-scoped cache keys и invalidation должны изолировать данные разных identities и scopes без использования secret как публичного key contract.
|
|
||||||
|
|
||||||
Эта draft-версия не предписывает единственное физическое место QueryClient/SWR cache. Конкретная модель оценивается по правилам public boundary владельца, lifecycle и identity isolation.
|
|
||||||
|
|
||||||
**SLM-DATA-015 - ОБЯЗАН.** Cache instance должен иметь явного creator и scope owner в composition или runtime setup.
|
|
||||||
|
|
||||||
**SLM-DATA-016 - ОБЯЗАН.** Shared framework cache должен передаваться consumers через framework-supported runtime boundary, а не через import app-specific mutable singleton.
|
|
||||||
|
|
||||||
**SLM-DATA-017 - ОБЯЗАН.** Scope owner должен очищать или изолировать private cache при смене identity и завершении соответствующего scope.
|
|
||||||
|
|
||||||
## Presentation state
|
|
||||||
|
|
||||||
**SLM-DATA-010 - МОЖЕТ.** Composition или component может использовать concrete state manager для локального presentation state.
|
|
||||||
|
|
||||||
**SLM-DATA-011 - ЗАПРЕЩЕНО.** Presentation store не должен копировать canonical product state как второй source of truth.
|
|
||||||
|
|
||||||
## Serializable boundaries
|
|
||||||
|
|
||||||
**SLM-DATA-012 - ОБЯЗАН.** Через server/client boundary передаются только serializable product-owned data без functions, stores, clients, Context и resources.
|
|
||||||
|
|
||||||
**SLM-DATA-013 - ЗАПРЕЩЕНО.** Secrets, access tokens и request credentials не должны включаться в client bootstrap snapshot.
|
|
||||||
|
|
||||||
**SLM-DATA-014 - ОБЯЗАН.** Server и client initial snapshots должны быть согласованы, если framework выполняет hydration одного UI state.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
title: Тестирование и соответствие
|
|
||||||
status: draft
|
|
||||||
normative: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Тестирование и Соответствие
|
|
||||||
|
|
||||||
Тесты проверяют public boundaries и runtime risks каждого owner. Base SLM не требует создавать неиспользуемые архитектурные конструкции ради тестовой формы.
|
|
||||||
|
|
||||||
## Risk-based tests
|
|
||||||
|
|
||||||
**SLM-TEST-018 - ОБЯЗАН.** Tests изменённого module должны покрывать применимые риски его public behavior, data boundaries и lifecycle.
|
|
||||||
|
|
||||||
Типичные риски:
|
|
||||||
|
|
||||||
- public behavior;
|
|
||||||
- malformed external data;
|
|
||||||
- rejected dependencies;
|
|
||||||
- state transitions;
|
|
||||||
- lifecycle activation и cleanup;
|
|
||||||
- request и identity isolation;
|
|
||||||
- client/server boundary;
|
|
||||||
- отсутствие import-time I/O.
|
|
||||||
|
|
||||||
**SLM-TEST-008 - ОБЯЗАН.** Client/server import boundary должна проверяться инструментом, понимающим реальный framework module graph, если application имеет раздельные environment entries. DOM unit test не заменяет production build probe.
|
|
||||||
|
|
||||||
Mode-specific test suites принадлежат overlay, который вводит соответствующие конструкции.
|
|
||||||
|
|
||||||
## Architecture conformance
|
|
||||||
|
|
||||||
Типичные mechanically enforceable checks:
|
|
||||||
|
|
||||||
- направление imports;
|
|
||||||
- deep imports;
|
|
||||||
- public entrypoints;
|
|
||||||
- runtime cycles;
|
|
||||||
- заявленный overlay и его rule set;
|
|
||||||
- unique rule IDs документации;
|
|
||||||
- generated artifacts, если они используются.
|
|
||||||
|
|
||||||
**SLM-TEST-011 - ЗАПРЕЩЕНО.** Документированное правило не считается mechanically enforced, если repository tooling его фактически не проверяет.
|
|
||||||
|
|
||||||
## Единица соответствия
|
|
||||||
|
|
||||||
**SLM-TEST-014 - ОБЯЗАН.** Application соответствует base SLM, если выполняет все base-правила. Соответствие заявленному overlay оценивается как base-правила с учётом точного scope каждой замены плюс полный rule set выбранного overlay.
|
|
||||||
|
|
||||||
**SLM-TEST-015 - ОБЯЗАН.** Изменение соответствует заявленной архитектуре, если новые и изменённые modules не создают новых нарушений применимых base-правил или правил выбранного overlay и проходят существующие checks.
|
|
||||||
|
|
||||||
**SLM-TEST-016 - ОБЯЗАН.** Отступление от правила `СЛЕДУЕТ` должно быть зафиксировано в архитектурном review или принятом decision с указанием причины и scope.
|
|
||||||
|
|
||||||
**SLM-TEST-017 - ОБЯЗАН.** Manual conformance и mechanical enforcement должны различаться явно; отсутствие автоматической проверки не отменяет применимое нормативное правило.
|
|
||||||
|
|
||||||
## Completion gate
|
|
||||||
|
|
||||||
**SLM-TEST-012 - ОБЯЗАН.** Изменение считается завершённым только после выполнения ближайших tests, typecheck, lint, build и architecture checks, существующих в repository.
|
|
||||||
|
|
||||||
**SLM-TEST-013 - ОБЯЗАН.** Невыполненная проверка и остаточный риск должны быть явно указаны в результате работы.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# SLM Design
|
# SLM Design
|
||||||
|
|
||||||
`docs/` - файлы документации по SLM-архитектуре.
|
Этот каталог содержит действующую legacy-документацию по SLM-архитектуре.
|
||||||
|
|
||||||
Используй эту документацию как источник истины для задач по SLM Design: слоям, модулям, сегментам, публичному API, business factory, composition modules и монорепозиториям.
|
Используй эту документацию как источник истины для задач по SLM Design: слоям, модулям, сегментам, публичному API, business factory, composition modules и монорепозиториям.
|
||||||
|
|
||||||
2521
package-lock.json
generated
Normal file
2521
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -5,10 +5,20 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:skill",
|
"build": "npm run build:skill",
|
||||||
"build:skill": "node scripts/build-skill.mjs",
|
"build:skill": "node scripts/build-skill.mjs",
|
||||||
"check": "npm run build && npm run check:skill",
|
"check": "npm run build && npm run check:skill && npm run check:docs-all",
|
||||||
"check:skill": "node scripts/check-skill.mjs"
|
"check:skill": "node scripts/check-skill.mjs",
|
||||||
|
"check:docs": "node scripts/check-docs.mjs",
|
||||||
|
"check:docs-search": "node scripts/check-docs-search.mjs",
|
||||||
|
"check:docs-all": "npm run check:docs && npm run docs:build && npm run check:docs-search",
|
||||||
|
"docs:dev": "vitepress dev site",
|
||||||
|
"docs:build": "vitepress build site",
|
||||||
|
"docs:preview": "vitepress preview site"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"minisearch": "7.2.0",
|
||||||
|
"vitepress": "1.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import skillConfig from '../src-skills/slm-design/skill.config.mjs';
|
|||||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
const sourceDir = path.join(repoRoot, 'src-skills', skillConfig.name);
|
const sourceDir = path.join(repoRoot, 'src-skills', skillConfig.name);
|
||||||
const sourcePath = path.join(sourceDir, skillConfig.source);
|
const sourcePath = path.join(sourceDir, skillConfig.source);
|
||||||
const docsDir = path.join(repoRoot, 'docs');
|
const legacyDocsDir = path.join(repoRoot, 'old-docs');
|
||||||
const outputDir = path.join(repoRoot, 'skills', skillConfig.name);
|
const outputDir = path.join(repoRoot, 'skills', skillConfig.name);
|
||||||
const includePattern = /<!--\s*include:\s*(.*?)\s*-->/g;
|
const includePattern = /<!--\s*include:\s*(.*?)\s*-->/g;
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ if (!fs.existsSync(sourcePath)) {
|
|||||||
throw new Error(`Skill source not found: ${path.relative(repoRoot, sourcePath)}`);
|
throw new Error(`Skill source not found: ${path.relative(repoRoot, sourcePath)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(docsDir)) {
|
if (!fs.existsSync(legacyDocsDir)) {
|
||||||
throw new Error('Documentation directory not found: docs');
|
throw new Error('Legacy documentation directory not found: old-docs');
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = fs.readFileSync(sourcePath, 'utf8');
|
const source = fs.readFileSync(sourcePath, 'utf8');
|
||||||
@@ -65,6 +65,6 @@ const output = [
|
|||||||
fs.rmSync(outputDir, { recursive: true, force: true });
|
fs.rmSync(outputDir, { recursive: true, force: true });
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
fs.mkdirSync(outputDir, { recursive: true });
|
||||||
fs.writeFileSync(path.join(outputDir, 'SKILL.md'), `${output}\n`);
|
fs.writeFileSync(path.join(outputDir, 'SKILL.md'), `${output}\n`);
|
||||||
fs.cpSync(docsDir, path.join(outputDir, 'reference'), { recursive: true });
|
fs.cpSync(legacyDocsDir, path.join(outputDir, 'reference'), { recursive: true });
|
||||||
|
|
||||||
console.log(path.relative(repoRoot, path.join(outputDir, 'SKILL.md')));
|
console.log(path.relative(repoRoot, path.join(outputDir, 'SKILL.md')));
|
||||||
|
|||||||
88
scripts/check-docs-search.mjs
Normal file
88
scripts/check-docs-search.mjs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { readFile, readdir } from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||||
|
import MiniSearch from 'minisearch'
|
||||||
|
import { collectRules, RULE_SEARCH_OPTIONS } from './lib/specification.mjs'
|
||||||
|
|
||||||
|
const repoRoot = fileURLToPath(new URL('../', import.meta.url))
|
||||||
|
const specificationRoot = path.join(repoRoot, 'docs', 'ru', 'specification')
|
||||||
|
const distRoot = path.join(repoRoot, 'site', '.vitepress', 'dist')
|
||||||
|
const chunksDirectory = path.join(distRoot, 'assets', 'chunks')
|
||||||
|
const rules = await collectRules(specificationRoot)
|
||||||
|
const chunkNames = (await readdir(chunksDirectory))
|
||||||
|
.filter((file) => file.startsWith('@localSearchIndexru.') && file.endsWith('.js'))
|
||||||
|
|
||||||
|
if (chunkNames.length !== 1) {
|
||||||
|
throw new Error(`Expected one Russian search index chunk, found ${chunkNames.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchData = (
|
||||||
|
await import(`${pathToFileURL(path.join(chunksDirectory, chunkNames[0])).href}?t=${Date.now()}`)
|
||||||
|
).default
|
||||||
|
const searchIndex = MiniSearch.loadJSON(searchData, {
|
||||||
|
fields: ['title', 'titles', 'text'],
|
||||||
|
storeFields: ['title', 'titles'],
|
||||||
|
})
|
||||||
|
const catalogHtml = await readFile(
|
||||||
|
path.join(distRoot, 'ru', 'specification', 'rules.html'),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
const htmlCache = new Map()
|
||||||
|
|
||||||
|
function pageHtmlPath(pageHref) {
|
||||||
|
const route = pageHref.replace(/^\/ru\/specification\/?/, '')
|
||||||
|
return route.endsWith('/') || route === ''
|
||||||
|
? path.join(distRoot, 'ru', 'specification', route, 'index.html')
|
||||||
|
: path.join(distRoot, 'ru', 'specification', `${route}.html`)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
const expectedId = `/slm-design${rule.href}`
|
||||||
|
const firstResult = searchIndex.search(rule.id, RULE_SEARCH_OPTIONS)[0]
|
||||||
|
|
||||||
|
if (firstResult?.id !== expectedId) {
|
||||||
|
throw new Error(
|
||||||
|
`Search for ${rule.id} returned ${firstResult?.id || 'nothing'} instead of ${expectedId}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstResult.title.startsWith(rule.id)) {
|
||||||
|
throw new Error(`Search title for ${rule.id} does not start with the exact rule ID`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstResult.titles?.[0] !== 'Спецификация') {
|
||||||
|
throw new Error(`Search breadcrumb for ${rule.id} does not identify Specification`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlPath = pageHtmlPath(rule.pageHref)
|
||||||
|
let html = htmlCache.get(htmlPath)
|
||||||
|
if (!html) {
|
||||||
|
html = await readFile(htmlPath, 'utf8')
|
||||||
|
htmlCache.set(htmlPath, html)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!html.includes(`id="${rule.anchor}"`)) {
|
||||||
|
throw new Error(`Missing HTML anchor for ${rule.id} in ${rule.relativePath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!html.includes(`class="slm-rule__permalink" href="#${rule.anchor}"`)) {
|
||||||
|
throw new Error(`Missing permalink for ${rule.id} in ${rule.relativePath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!catalogHtml.includes(`>${rule.id}</a>`)) {
|
||||||
|
throw new Error(`Rule catalog does not contain ${rule.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const representativePage = await readFile(
|
||||||
|
path.join(distRoot, 'ru', 'specification', 'foundations.html'),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!representativePage.includes('class="doc-set-header"')) {
|
||||||
|
throw new Error('Specification sidebar does not contain the document-set header')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Documentation search check passed: ${rules.length} exact rule queries, anchors, permalinks, and catalog entries.`,
|
||||||
|
)
|
||||||
118
scripts/check-docs.mjs
Normal file
118
scripts/check-docs.mjs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import {
|
||||||
|
collectMarkdownFiles,
|
||||||
|
collectRules,
|
||||||
|
RULE_ID_PATTERN_SOURCE,
|
||||||
|
RULE_LEVEL_PATTERN_SOURCE,
|
||||||
|
} from './lib/specification.mjs'
|
||||||
|
|
||||||
|
const specificationRoot = fileURLToPath(
|
||||||
|
new URL('../docs/ru/specification/', import.meta.url),
|
||||||
|
)
|
||||||
|
|
||||||
|
const declarationPattern = new RegExp(
|
||||||
|
`^\\*\\*(${RULE_ID_PATTERN_SOURCE}) - (${RULE_LEVEL_PATTERN_SOURCE})\\.\\*\\*`,
|
||||||
|
'gm',
|
||||||
|
)
|
||||||
|
const declarationLinePattern = new RegExp(
|
||||||
|
`^\\*\\*(${RULE_ID_PATTERN_SOURCE}) - (${RULE_LEVEL_PATTERN_SOURCE})\\.\\*\\*`,
|
||||||
|
)
|
||||||
|
const referencePattern = new RegExp(`\\b${RULE_ID_PATTERN_SOURCE}\\b`, 'g')
|
||||||
|
const legacyBaseReferencePattern = /\bSLM-(?!BASE-|ADV-|PRO-)[A-Z][A-Z0-9]*-\d{3}\b/g
|
||||||
|
|
||||||
|
function lineNumberAt(content, index) {
|
||||||
|
return content.slice(0, index).split('\n').length
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = await collectMarkdownFiles(specificationRoot)
|
||||||
|
const registry = await collectRules(specificationRoot)
|
||||||
|
const declarations = new Map()
|
||||||
|
const references = []
|
||||||
|
const errors = []
|
||||||
|
const ruleCounts = { BASE: 0, ADV: 0, PRO: 0 }
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const content = await readFile(file, 'utf8')
|
||||||
|
const relativePath = path.relative(specificationRoot, file).split(path.sep).join('/')
|
||||||
|
const lines = content.split('\n')
|
||||||
|
|
||||||
|
for (const [index, line] of lines.entries()) {
|
||||||
|
if (line.startsWith('**SLM-') && !declarationLinePattern.test(line)) {
|
||||||
|
errors.push(`${relativePath}:${index + 1}: malformed rule declaration`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const match of content.matchAll(declarationPattern)) {
|
||||||
|
const id = match[1]
|
||||||
|
const location = `${relativePath}:${lineNumberAt(content, match.index)}`
|
||||||
|
const existingLocation = declarations.get(id)
|
||||||
|
|
||||||
|
if (existingLocation) {
|
||||||
|
errors.push(`${location}: duplicate ${id}; first declared at ${existingLocation}`)
|
||||||
|
} else {
|
||||||
|
declarations.set(id, location)
|
||||||
|
ruleCounts[id.split('-')[1]] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith('SLM-BASE-') && relativePath.startsWith('modes/')) {
|
||||||
|
errors.push(`${location}: base rule ${id} cannot be declared in an overlay`)
|
||||||
|
} else if (id.startsWith('SLM-ADV-') && !relativePath.startsWith('modes/advanced/')) {
|
||||||
|
errors.push(`${location}: ${id} must be declared under modes/advanced`)
|
||||||
|
} else if (id.startsWith('SLM-PRO-') && !relativePath.startsWith('modes/pro/')) {
|
||||||
|
errors.push(`${location}: ${id} must be declared under modes/pro`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const match of content.matchAll(referencePattern)) {
|
||||||
|
references.push({
|
||||||
|
id: match[0],
|
||||||
|
location: `${relativePath}:${lineNumberAt(content, match.index)}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const match of content.matchAll(legacyBaseReferencePattern)) {
|
||||||
|
errors.push(
|
||||||
|
`${relativePath}:${lineNumberAt(content, match.index)}: legacy base rule ID ${match[0]}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relativePath.startsWith('modes/advanced/') && /\bSLM-PRO-[A-Z-]+-\d{3}\b/.test(content)) {
|
||||||
|
errors.push(`${relativePath}: Advanced overlay references a Pro rule`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relativePath.startsWith('modes/pro/') && /\bSLM-ADV-[A-Z-]+-\d{3}\b/.test(content)) {
|
||||||
|
errors.push(`${relativePath}: Pro overlay references an Advanced rule`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const reference of references) {
|
||||||
|
if (!declarations.has(reference.id)) {
|
||||||
|
errors.push(`${reference.location}: unknown rule reference ${reference.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registry.length !== declarations.size) {
|
||||||
|
errors.push(
|
||||||
|
`rule registry contains ${registry.length} records, but validator found ${declarations.size} declarations`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rule of registry) {
|
||||||
|
if (!declarations.has(rule.id)) {
|
||||||
|
errors.push(`${rule.relativePath}:${rule.line}: registry contains undeclared rule ${rule.id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error(`Documentation check failed with ${errors.length} error(s):`)
|
||||||
|
for (const error of errors) console.error(`- ${error}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`Documentation check passed: ${files.length} files, ${declarations.size} rules `
|
||||||
|
+ `(${ruleCounts.BASE} base, ${ruleCounts.ADV} advanced, ${ruleCounts.PRO} pro), `
|
||||||
|
+ `${references.length} rule occurrences.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
129
scripts/lib/specification.mjs
Normal file
129
scripts/lib/specification.mjs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { readdir, readFile } from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
export const RULE_ID_PATTERN_SOURCE = 'SLM-(?:BASE|ADV|PRO)-[A-Z][A-Z0-9]*-\\d{3}'
|
||||||
|
export const RULE_LEVEL_PATTERN_SOURCE = 'ОБЯЗАН|ЗАПРЕЩЕНО|СЛЕДУЕТ|МОЖЕТ'
|
||||||
|
export const RULE_SEARCH_OPTIONS = {
|
||||||
|
fuzzy: false,
|
||||||
|
prefix: true,
|
||||||
|
combineWith: 'AND',
|
||||||
|
boost: { title: 50, text: 2, titles: 1 },
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleIdPattern = /^SLM-(BASE|ADV|PRO)-([A-Z][A-Z0-9]*)-(\d{3})$/
|
||||||
|
const declarationPattern = new RegExp(
|
||||||
|
`^\\*\\*(${RULE_ID_PATTERN_SOURCE}) - (${RULE_LEVEL_PATTERN_SOURCE})\\.\\*\\*\\s+(.+?)\\s*$`,
|
||||||
|
)
|
||||||
|
const rulesetOrder = { BASE: 0, ADV: 1, PRO: 2 }
|
||||||
|
|
||||||
|
export async function collectMarkdownFiles(directory) {
|
||||||
|
const entries = await readdir(directory, { withFileTypes: true })
|
||||||
|
const files = []
|
||||||
|
|
||||||
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
||||||
|
const entryPath = path.join(directory, entry.name)
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
files.push(...await collectMarkdownFiles(entryPath))
|
||||||
|
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||||
|
files.push(entryPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRuleId(id) {
|
||||||
|
const match = id.match(ruleIdPattern)
|
||||||
|
if (!match) return null
|
||||||
|
|
||||||
|
return {
|
||||||
|
ruleset: match[1],
|
||||||
|
area: match[2],
|
||||||
|
number: Number(match[3]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRuleDeclaration(line) {
|
||||||
|
const match = line.match(declarationPattern)
|
||||||
|
if (!match) return null
|
||||||
|
|
||||||
|
const parsedId = parseRuleId(match[1])
|
||||||
|
if (!parsedId) return null
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: match[1],
|
||||||
|
...parsedId,
|
||||||
|
level: match[2],
|
||||||
|
markdown: match[3],
|
||||||
|
text: stripInlineMarkdown(match[3]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stripInlineMarkdown(value) {
|
||||||
|
return value
|
||||||
|
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
|
||||||
|
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
||||||
|
.replace(/`([^`]+)`/g, '$1')
|
||||||
|
.replace(/<[^>]+>/g, '')
|
||||||
|
.replace(/[*_~]/g, '')
|
||||||
|
.replace(/\\([\\`*{}\[\]()#+.!_-])/g, '$1')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function specificationPathToHref(relativePath, basePath = '/ru/specification/') {
|
||||||
|
let route = relativePath.split(path.sep).join('/')
|
||||||
|
|
||||||
|
if (route === 'index.md') route = ''
|
||||||
|
else route = route.replace(/(?:^|\/)index\.md$/, '/').replace(/\.md$/, '')
|
||||||
|
|
||||||
|
return `${basePath}${route}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function collectRules(specificationRoot, basePath = '/ru/specification/') {
|
||||||
|
const files = await collectMarkdownFiles(specificationRoot)
|
||||||
|
const rules = []
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const content = await readFile(file, 'utf8')
|
||||||
|
const relativePath = path.relative(specificationRoot, file).split(path.sep).join('/')
|
||||||
|
const headings = []
|
||||||
|
|
||||||
|
for (const [lineIndex, line] of content.split('\n').entries()) {
|
||||||
|
const heading = line.match(/^(#{1,6})\s+(.+?)\s*$/)
|
||||||
|
if (heading) {
|
||||||
|
const level = heading[1].length
|
||||||
|
headings.length = level
|
||||||
|
headings[level - 1] = stripInlineMarkdown(heading[2])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const rule = parseRuleDeclaration(line)
|
||||||
|
if (!rule) continue
|
||||||
|
|
||||||
|
const pageTitle = headings[0] || relativePath
|
||||||
|
const sectionTitles = headings.slice(1).filter(Boolean)
|
||||||
|
const pageHref = specificationPathToHref(relativePath, basePath)
|
||||||
|
|
||||||
|
rules.push({
|
||||||
|
...rule,
|
||||||
|
anchor: rule.id.toLowerCase(),
|
||||||
|
href: `${pageHref}#${rule.id.toLowerCase()}`,
|
||||||
|
line: lineIndex + 1,
|
||||||
|
pageHref,
|
||||||
|
pageTitle,
|
||||||
|
relativePath,
|
||||||
|
sectionTitle: sectionTitles.at(-1) || pageTitle,
|
||||||
|
sectionTitles,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules.sort((left, right) => (
|
||||||
|
rulesetOrder[left.ruleset] - rulesetOrder[right.ruleset]
|
||||||
|
|| left.area.localeCompare(right.area)
|
||||||
|
|| left.number - right.number
|
||||||
|
|| left.id.localeCompare(right.id)
|
||||||
|
))
|
||||||
|
}
|
||||||
269
site/.vitepress/config.mts
Normal file
269
site/.vitepress/config.mts
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
import { defineConfig } from 'vitepress'
|
||||||
|
import type MarkdownIt from 'markdown-it'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { splitSearchSections } from './search.mts'
|
||||||
|
import { RULE_SEARCH_OPTIONS } from '../../scripts/lib/specification.mjs'
|
||||||
|
|
||||||
|
const repositoryUrl = 'https://github.com/gromlab-ru/slm-design'
|
||||||
|
const viteConfigPath = fileURLToPath(new URL('../vite.config.mts', import.meta.url))
|
||||||
|
|
||||||
|
const specificationSidebar = [
|
||||||
|
{
|
||||||
|
text: 'Начало',
|
||||||
|
items: [
|
||||||
|
{ text: 'Обзор спецификации', link: '/ru/specification/' },
|
||||||
|
{ text: 'Architecture modes', link: '/ru/specification/architecture-modes' },
|
||||||
|
{ text: 'Реестр правил', link: '/ru/specification/rules' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Base SLM',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'Основы',
|
||||||
|
items: [
|
||||||
|
{ text: 'Основные инварианты', link: '/ru/specification/foundations' },
|
||||||
|
{ text: 'Терминология', link: '/ru/specification/terminology' },
|
||||||
|
{ text: 'Архитектурная модель', link: '/ru/specification/architecture-model' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Слои',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Обзор', link: '/ru/specification/layers/' },
|
||||||
|
{ text: 'App', link: '/ru/specification/layers/app' },
|
||||||
|
{ text: 'Compositions', link: '/ru/specification/layers/compositions' },
|
||||||
|
{ text: 'Infra', link: '/ru/specification/layers/infra' },
|
||||||
|
{ text: 'UI', link: '/ru/specification/layers/ui' },
|
||||||
|
{ text: 'Shared', link: '/ru/specification/layers/shared' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Общие правила',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Модули и группы', link: '/ru/specification/modules-and-groups' },
|
||||||
|
{ text: 'Сегменты', link: '/ru/specification/segments' },
|
||||||
|
{ text: 'Public API и импорты', link: '/ru/specification/public-api-and-imports' },
|
||||||
|
{ text: 'State и data', link: '/ru/specification/state-and-data' },
|
||||||
|
{ text: 'Runtime и lifecycle', link: '/ru/specification/runtime-and-lifecycle' },
|
||||||
|
{ text: 'Тестирование', link: '/ru/specification/testing-and-conformance' },
|
||||||
|
{ text: 'Монорепозитории', link: '/ru/specification/monorepo' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Overlays',
|
||||||
|
collapsed: false,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'SLM Advanced',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Advanced overlay', link: '/ru/specification/modes/advanced/' },
|
||||||
|
{ text: 'Domains', link: '/ru/specification/modes/advanced/domains' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'SLM Pro',
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Pro overlay', link: '/ru/specification/modes/pro/' },
|
||||||
|
{ text: 'Domains', link: '/ru/specification/modes/pro/domains/' },
|
||||||
|
{ text: 'Business', link: '/ru/specification/modes/pro/domains/business' },
|
||||||
|
{ text: 'Framework surface', link: '/ru/specification/modes/pro/domains/framework' },
|
||||||
|
{ text: 'Ports и adapters', link: '/ru/specification/modes/pro/domains/ports-and-adapters' },
|
||||||
|
{ text: 'Client и server', link: '/ru/specification/modes/pro/domains/client-and-server' },
|
||||||
|
{ text: 'Cross-domain boundary', link: '/ru/specification/modes/pro/domains/cross-domain-boundary' },
|
||||||
|
{ text: 'Тестирование domains', link: '/ru/specification/modes/pro/domains/testing' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function addRuleAnchors(md: MarkdownIt) {
|
||||||
|
const rulePattern = /^\*\*(SLM-(?:BASE|ADV|PRO)-[A-Z][A-Z-]*-\d{3}) - (ОБЯЗАН|ЗАПРЕЩЕНО|СЛЕДУЕТ|МОЖЕТ)\.\*\*/
|
||||||
|
const kindByKeyword: Record<string, string> = {
|
||||||
|
ОБЯЗАН: 'required',
|
||||||
|
ЗАПРЕЩЕНО: 'prohibited',
|
||||||
|
СЛЕДУЕТ: 'recommended',
|
||||||
|
МОЖЕТ: 'optional',
|
||||||
|
}
|
||||||
|
|
||||||
|
md.core.ruler.after('inline', 'slm-rule-anchors', (state) => {
|
||||||
|
for (let index = 0; index < state.tokens.length - 1; index += 1) {
|
||||||
|
const paragraph = state.tokens[index]
|
||||||
|
const content = state.tokens[index + 1]
|
||||||
|
|
||||||
|
if (paragraph.type !== 'paragraph_open' || content.type !== 'inline') continue
|
||||||
|
|
||||||
|
const match = content.content.match(rulePattern)
|
||||||
|
if (!match) continue
|
||||||
|
|
||||||
|
paragraph.attrSet('id', match[1].toLowerCase())
|
||||||
|
paragraph.attrJoin('class', 'slm-rule')
|
||||||
|
paragraph.attrJoin('class', `slm-rule--${kindByKeyword[match[2]]}`)
|
||||||
|
|
||||||
|
const strongCloseIndex = content.children?.findIndex((token) => token.type === 'strong_close') ?? -1
|
||||||
|
if (strongCloseIndex < 0 || !content.children) continue
|
||||||
|
|
||||||
|
const permalinkOpen = new state.Token('link_open', 'a', 1)
|
||||||
|
permalinkOpen.attrSet('class', 'slm-rule__permalink')
|
||||||
|
permalinkOpen.attrSet('href', `#${match[1].toLowerCase()}`)
|
||||||
|
permalinkOpen.attrSet('aria-label', `Ссылка на правило ${match[1]}`)
|
||||||
|
permalinkOpen.attrSet('title', `Ссылка на ${match[1]}`)
|
||||||
|
|
||||||
|
const permalinkText = new state.Token('text', '', 0)
|
||||||
|
permalinkText.content = '#'
|
||||||
|
|
||||||
|
const permalinkClose = new state.Token('link_close', 'a', -1)
|
||||||
|
content.children.splice(
|
||||||
|
strongCloseIndex + 1,
|
||||||
|
0,
|
||||||
|
permalinkOpen,
|
||||||
|
permalinkText,
|
||||||
|
permalinkClose,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
srcDir: '../docs',
|
||||||
|
title: 'SLM Design',
|
||||||
|
description: 'Specification for explicit architecture boundaries in product applications',
|
||||||
|
lang: 'en-US',
|
||||||
|
base: '/slm-design/',
|
||||||
|
cleanUrls: true,
|
||||||
|
lastUpdated: true,
|
||||||
|
sitemap: {
|
||||||
|
hostname: 'https://gromlab-ru.github.io/slm-design/',
|
||||||
|
transformItems: (items) => items.map((item) => {
|
||||||
|
if (!item.links?.some((link) => link.url === 'ru/')) return item
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
links: [
|
||||||
|
{ lang: 'x-default', url: '' },
|
||||||
|
...item.links.filter((link) => link.url !== ''),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
head: [
|
||||||
|
['link', { rel: 'icon', type: 'image/svg+xml', href: '/slm-design/logo.svg' }],
|
||||||
|
['meta', { name: 'theme-color', content: '#d97706' }],
|
||||||
|
],
|
||||||
|
markdown: {
|
||||||
|
config: addRuleAnchors,
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
configFile: viteConfigPath,
|
||||||
|
},
|
||||||
|
themeConfig: {
|
||||||
|
logo: '/logo.svg',
|
||||||
|
siteTitle: 'SLM Design',
|
||||||
|
i18nRouting: false,
|
||||||
|
nav: [
|
||||||
|
{ text: 'Русская спецификация', link: '/ru/' },
|
||||||
|
{ text: 'English', link: '/en/' },
|
||||||
|
],
|
||||||
|
socialLinks: [{ icon: 'github', link: repositoryUrl }],
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
options: {
|
||||||
|
detailedView: false,
|
||||||
|
disableQueryPersistence: true,
|
||||||
|
miniSearch: {
|
||||||
|
searchOptions: RULE_SEARCH_OPTIONS,
|
||||||
|
_splitIntoSections: splitSearchSections,
|
||||||
|
},
|
||||||
|
locales: {
|
||||||
|
ru: {
|
||||||
|
translations: {
|
||||||
|
button: {
|
||||||
|
buttonText: 'Поиск или rule ID',
|
||||||
|
buttonAriaLabel: 'Поиск по документации или rule ID',
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
displayDetails: 'Показать подробности',
|
||||||
|
resetButtonTitle: 'Сбросить поиск',
|
||||||
|
backButtonTitle: 'Закрыть поиск',
|
||||||
|
noResultsText: 'Ничего не найдено по запросу',
|
||||||
|
footer: {
|
||||||
|
selectText: 'выбрать',
|
||||||
|
navigateText: 'перейти',
|
||||||
|
closeText: 'закрыть',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
locales: {
|
||||||
|
ru: {
|
||||||
|
label: 'Русский',
|
||||||
|
lang: 'ru-RU',
|
||||||
|
link: '/ru/',
|
||||||
|
title: 'SLM Design',
|
||||||
|
description: 'Спецификация архитектурных границ продуктовых приложений',
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: 'Документация', link: '/ru/' },
|
||||||
|
{ text: 'Спецификация', link: '/ru/specification/' },
|
||||||
|
],
|
||||||
|
sidebar: {
|
||||||
|
'/ru/specification/': specificationSidebar,
|
||||||
|
},
|
||||||
|
outline: { level: [2, 3], label: 'На этой странице' },
|
||||||
|
editLink: {
|
||||||
|
pattern: `${repositoryUrl}/edit/master/docs/:path`,
|
||||||
|
text: 'Предложить изменение',
|
||||||
|
},
|
||||||
|
lastUpdated: {
|
||||||
|
text: 'Обновлено',
|
||||||
|
formatOptions: { dateStyle: 'medium' },
|
||||||
|
},
|
||||||
|
docFooter: {
|
||||||
|
prev: 'Предыдущая страница',
|
||||||
|
next: 'Следующая страница',
|
||||||
|
},
|
||||||
|
darkModeSwitchLabel: 'Оформление',
|
||||||
|
lightModeSwitchTitle: 'Светлая тема',
|
||||||
|
darkModeSwitchTitle: 'Тёмная тема',
|
||||||
|
sidebarMenuLabel: 'Содержание',
|
||||||
|
returnToTopLabel: 'Наверх',
|
||||||
|
langMenuLabel: 'Изменить язык',
|
||||||
|
skipToContentLabel: 'Перейти к содержанию',
|
||||||
|
footer: {
|
||||||
|
message: 'SLM Design 2.0 Draft',
|
||||||
|
copyright: 'Нормативная русская версия находится в статусе draft.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
label: 'English',
|
||||||
|
lang: 'en-US',
|
||||||
|
link: '/en/',
|
||||||
|
title: 'SLM Design',
|
||||||
|
description: 'English translation placeholder for the SLM Design specification',
|
||||||
|
themeConfig: {
|
||||||
|
nav: [
|
||||||
|
{ text: 'English status', link: '/en/' },
|
||||||
|
{ text: 'Russian specification', link: '/ru/specification/' },
|
||||||
|
],
|
||||||
|
outline: { level: [2, 3], label: 'On this page' },
|
||||||
|
footer: {
|
||||||
|
message: 'SLM Design 2.0 Draft',
|
||||||
|
copyright: 'The English edition is not normative yet.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
14
site/.vitepress/rules.data.mts
Normal file
14
site/.vitepress/rules.data.mts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { defineLoader } from 'vitepress'
|
||||||
|
import { collectRules } from '../../scripts/lib/specification.mjs'
|
||||||
|
|
||||||
|
const specificationRoot = fileURLToPath(
|
||||||
|
new URL('../../docs/ru/specification/', import.meta.url),
|
||||||
|
)
|
||||||
|
|
||||||
|
export default defineLoader({
|
||||||
|
watch: '../../docs/ru/specification/**/*.md',
|
||||||
|
async load() {
|
||||||
|
return collectRules(specificationRoot)
|
||||||
|
},
|
||||||
|
})
|
||||||
61
site/.vitepress/search.mts
Normal file
61
site/.vitepress/search.mts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
const headingPattern = /<h(\d).*?>(.*?<a.*? href="#.*?".*?>.*?<\/a>)<\/h\1>/gi
|
||||||
|
const headingContentPattern = /(.*?)<a.*? href="#(.*?)".*?>.*?<\/a>/i
|
||||||
|
const ruleBlockPattern = /<p id="(slm-(?:base|adv|pro)-[a-z][a-z0-9]*-\d{3})" class="[^"]*\bslm-rule\b[^"]*"[^>]*>([\s\S]*?)<\/p>/gi
|
||||||
|
const rulePermalinkPattern = /<a\b[^>]*class="[^"]*\bslm-rule__permalink\b[^"]*"[^>]*>[\s\S]*?<\/a>/i
|
||||||
|
|
||||||
|
function clearHtml(value: string) {
|
||||||
|
return value.replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRulesSearchable(html: string) {
|
||||||
|
return html.replace(ruleBlockPattern, (_block, anchor: string, innerHtml: string) => {
|
||||||
|
const strong = innerHtml.match(/<strong>([\s\S]*?)<\/strong>/i)
|
||||||
|
const label = clearHtml(strong?.[1] || anchor.toUpperCase())
|
||||||
|
const bodyHtml = innerHtml
|
||||||
|
.replace(/<strong>[\s\S]*?<\/strong>/i, '')
|
||||||
|
.replace(rulePermalinkPattern, '')
|
||||||
|
.trim()
|
||||||
|
return `<h6>${label}<a href="#${anchor}"></a></h6><p>${bodyHtml}</p>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function* splitByHeadings(html: string) {
|
||||||
|
const parts = html.split(headingPattern)
|
||||||
|
parts.shift()
|
||||||
|
let parentTitles: string[] = []
|
||||||
|
|
||||||
|
for (let index = 0; index < parts.length; index += 3) {
|
||||||
|
const level = Number.parseInt(parts[index], 10) - 1
|
||||||
|
const heading = headingContentPattern.exec(parts[index + 1])
|
||||||
|
const title = clearHtml(heading?.[1] || '')
|
||||||
|
const anchor = heading?.[2] || ''
|
||||||
|
const text = clearHtml(parts[index + 2] || '')
|
||||||
|
|
||||||
|
if (!title || !text) continue
|
||||||
|
|
||||||
|
let titles = parentTitles.slice(0, level)
|
||||||
|
titles[level] = title
|
||||||
|
titles = titles.filter(Boolean)
|
||||||
|
|
||||||
|
yield { anchor, titles, text }
|
||||||
|
|
||||||
|
if (level === 0) parentTitles = [title]
|
||||||
|
else parentTitles[level] = title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* splitSearchSections(file: string, html: string) {
|
||||||
|
const normalizedFile = file.replaceAll('\\', '/')
|
||||||
|
const documentTitle = normalizedFile.includes('/ru/specification/')
|
||||||
|
? 'Спецификация'
|
||||||
|
: normalizedFile.includes('/ru/guide/')
|
||||||
|
? 'Архитектурный гайд'
|
||||||
|
: null
|
||||||
|
|
||||||
|
for (const section of splitByHeadings(makeRulesSearchable(html))) {
|
||||||
|
yield {
|
||||||
|
...section,
|
||||||
|
titles: documentTitle ? [documentTitle, ...section.titles] : section.titles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
site/.vitepress/theme/DocSetHeader.vue
Normal file
41
site/.vitepress/theme/DocSetHeader.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useData, withBase } from 'vitepress'
|
||||||
|
|
||||||
|
const { page } = useData()
|
||||||
|
|
||||||
|
const documentSet = computed(() => {
|
||||||
|
if (page.value.relativePath.startsWith('ru/specification/')) {
|
||||||
|
return {
|
||||||
|
eyebrow: 'Нормативный документ',
|
||||||
|
href: '/ru/specification/',
|
||||||
|
meta: 'DRAFT · v0.1.0',
|
||||||
|
title: 'SLM Design Specification',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.value.relativePath.startsWith('ru/guide/')) {
|
||||||
|
return {
|
||||||
|
eyebrow: 'Учебный материал',
|
||||||
|
href: '/ru/guide/',
|
||||||
|
meta: 'GUIDE',
|
||||||
|
title: 'SLM Architecture Guide',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="documentSet" class="doc-set-header">
|
||||||
|
<a class="doc-set-header__back" :href="withBase('/ru/')">Документация</a>
|
||||||
|
<a class="doc-set-header__title" :href="withBase(documentSet.href)">
|
||||||
|
{{ documentSet.title }}
|
||||||
|
</a>
|
||||||
|
<div class="doc-set-header__meta">
|
||||||
|
<span>{{ documentSet.eyebrow }}</span>
|
||||||
|
<span>{{ documentSet.meta }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
29
site/.vitepress/theme/Layout.vue
Normal file
29
site/.vitepress/theme/Layout.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useData } from 'vitepress'
|
||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import DocSetHeader from './DocSetHeader.vue'
|
||||||
|
|
||||||
|
const { Layout } = DefaultTheme
|
||||||
|
const { lang } = useData()
|
||||||
|
|
||||||
|
const bannerText = computed(() =>
|
||||||
|
lang.value.startsWith('ru')
|
||||||
|
? 'SLM 2.0 DRAFT / Не заменяет действующую документацию'
|
||||||
|
: 'SLM 2.0 DRAFT / Not the current stable documentation',
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Layout>
|
||||||
|
<template #layout-top>
|
||||||
|
<div class="draft-banner">
|
||||||
|
<span class="draft-banner__mark" aria-hidden="true" />
|
||||||
|
{{ bannerText }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #sidebar-nav-before>
|
||||||
|
<DocSetHeader />
|
||||||
|
</template>
|
||||||
|
</Layout>
|
||||||
|
</template>
|
||||||
271
site/.vitepress/theme/RuleCatalog.vue
Normal file
271
site/.vitepress/theme/RuleCatalog.vue
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { withBase } from 'vitepress'
|
||||||
|
import { data as rules } from '../rules.data.mts'
|
||||||
|
|
||||||
|
type Rule = (typeof rules)[number]
|
||||||
|
|
||||||
|
const query = ref('')
|
||||||
|
const ruleset = ref('ALL')
|
||||||
|
const area = ref('ALL')
|
||||||
|
const level = ref('ALL')
|
||||||
|
const copiedId = ref('')
|
||||||
|
|
||||||
|
const areas = [...new Set(rules.map((rule) => rule.area))].sort()
|
||||||
|
const levels = ['ОБЯЗАН', 'ЗАПРЕЩЕНО', 'СЛЕДУЕТ', 'МОЖЕТ']
|
||||||
|
|
||||||
|
function relevance(rule: Rule, normalizedQuery: string) {
|
||||||
|
const id = rule.id.toLowerCase()
|
||||||
|
if (id === normalizedQuery) return 0
|
||||||
|
if (id.startsWith(normalizedQuery)) return 1
|
||||||
|
if (id.includes(normalizedQuery)) return 2
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredRules = computed(() => {
|
||||||
|
const normalizedQuery = query.value.trim().toLowerCase()
|
||||||
|
|
||||||
|
return rules
|
||||||
|
.filter((rule) => ruleset.value === 'ALL' || rule.ruleset === ruleset.value)
|
||||||
|
.filter((rule) => area.value === 'ALL' || rule.area === area.value)
|
||||||
|
.filter((rule) => level.value === 'ALL' || rule.level === level.value)
|
||||||
|
.filter((rule) => {
|
||||||
|
if (!normalizedQuery) return true
|
||||||
|
return `${rule.id} ${rule.text} ${rule.pageTitle} ${rule.sectionTitle}`
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(normalizedQuery)
|
||||||
|
})
|
||||||
|
.sort((left, right) => relevance(left, normalizedQuery) - relevance(right, normalizedQuery))
|
||||||
|
})
|
||||||
|
|
||||||
|
async function copyRuleLink(rule: Rule) {
|
||||||
|
const url = new URL(withBase(rule.href), window.location.origin).href
|
||||||
|
await navigator.clipboard.writeText(url)
|
||||||
|
copiedId.value = rule.id
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (copiedId.value === rule.id) copiedId.value = ''
|
||||||
|
}, 1600)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="rule-catalog">
|
||||||
|
<div class="rule-catalog__controls">
|
||||||
|
<label class="rule-catalog__search">
|
||||||
|
<span>Правило или текст</span>
|
||||||
|
<input v-model="query" type="search" placeholder="SLM-BASE-FND-003" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<span>Rule set</span>
|
||||||
|
<select v-model="ruleset">
|
||||||
|
<option value="ALL">Все</option>
|
||||||
|
<option value="BASE">Base</option>
|
||||||
|
<option value="ADV">Advanced</option>
|
||||||
|
<option value="PRO">Pro</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<span>Area</span>
|
||||||
|
<select v-model="area">
|
||||||
|
<option value="ALL">Все</option>
|
||||||
|
<option v-for="item in areas" :key="item" :value="item">{{ item }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<span>Уровень</span>
|
||||||
|
<select v-model="level">
|
||||||
|
<option value="ALL">Все</option>
|
||||||
|
<option v-for="item in levels" :key="item" :value="item">{{ item }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-catalog__summary" aria-live="polite">
|
||||||
|
Найдено: <strong>{{ filteredRules.length }}</strong> из {{ rules.length }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rule-catalog__list">
|
||||||
|
<article v-for="rule in filteredRules" :key="rule.id" class="rule-catalog__item">
|
||||||
|
<div class="rule-catalog__item-head">
|
||||||
|
<a :href="withBase(rule.href)" class="rule-catalog__id">{{ rule.id }}</a>
|
||||||
|
<div class="rule-catalog__badges">
|
||||||
|
<span :class="`rule-catalog__badge rule-catalog__badge--${rule.ruleset.toLowerCase()}`">
|
||||||
|
{{ rule.ruleset }}
|
||||||
|
</span>
|
||||||
|
<span class="rule-catalog__badge">{{ rule.area }}</span>
|
||||||
|
<span class="rule-catalog__badge">{{ rule.level }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{{ rule.text }}</p>
|
||||||
|
|
||||||
|
<div class="rule-catalog__source">
|
||||||
|
<a :href="withBase(rule.href)">{{ rule.pageTitle }} · {{ rule.sectionTitle }}</a>
|
||||||
|
<button type="button" @click="copyRuleLink(rule)">
|
||||||
|
{{ copiedId === rule.id ? 'Скопировано' : 'Копировать ссылку' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.rule-catalog {
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(220px, 1fr) repeat(3, minmax(120px, 0.35fr));
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__controls label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__controls input,
|
||||||
|
.rule-catalog__controls select {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 6px;
|
||||||
|
outline: none;
|
||||||
|
background: var(--vp-c-bg);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__controls input:focus,
|
||||||
|
.rule-catalog__controls select:focus {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
box-shadow: 0 0 0 3px var(--vp-c-brand-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__summary {
|
||||||
|
margin: 14px 2px;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__list {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__item {
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__item p {
|
||||||
|
margin: 12px 0;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__item-head,
|
||||||
|
.rule-catalog__source,
|
||||||
|
.rule-catalog__badges {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__item-head,
|
||||||
|
.rule-catalog__source {
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__badges {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__id {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__badge {
|
||||||
|
padding: 3px 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--vp-c-default-soft);
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__badge--base {
|
||||||
|
background: var(--vp-c-brand-soft);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__source {
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__source a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__source button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 820px) {
|
||||||
|
.rule-catalog__controls {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__search {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.rule-catalog__controls {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__search {
|
||||||
|
grid-column: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__item-head,
|
||||||
|
.rule-catalog__source {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-catalog__badges {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
12
site/.vitepress/theme/index.ts
Normal file
12
site/.vitepress/theme/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import Layout from './Layout.vue'
|
||||||
|
import RuleCatalog from './RuleCatalog.vue'
|
||||||
|
import './style.css'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extends: DefaultTheme,
|
||||||
|
Layout,
|
||||||
|
enhanceApp({ app }) {
|
||||||
|
app.component('RuleCatalog', RuleCatalog)
|
||||||
|
},
|
||||||
|
}
|
||||||
248
site/.vitepress/theme/style.css
Normal file
248
site/.vitepress/theme/style.css
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
:root {
|
||||||
|
--vp-layout-top-height: 34px;
|
||||||
|
--vp-c-brand-1: #b45309;
|
||||||
|
--vp-c-brand-2: #d97706;
|
||||||
|
--vp-c-brand-3: #f59e0b;
|
||||||
|
--vp-c-brand-soft: rgba(217, 119, 6, 0.14);
|
||||||
|
--vp-c-bg: #f8f7f4;
|
||||||
|
--vp-c-bg-alt: #efede8;
|
||||||
|
--vp-c-bg-elv: #ffffff;
|
||||||
|
--vp-c-bg-soft: #f1efe9;
|
||||||
|
--vp-font-family-base: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
--vp-font-family-mono: "IBM Plex Mono", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||||
|
--vp-home-hero-name-color: transparent;
|
||||||
|
--vp-home-hero-name-background: linear-gradient(120deg, #92400e 5%, #d97706 50%, #f59e0b 95%);
|
||||||
|
--vp-home-hero-image-background-image: radial-gradient(circle, rgba(245, 158, 11, 0.32), transparent 68%);
|
||||||
|
--vp-home-hero-image-filter: blur(56px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--vp-c-brand-1: #fbbf24;
|
||||||
|
--vp-c-brand-2: #f59e0b;
|
||||||
|
--vp-c-brand-3: #d97706;
|
||||||
|
--vp-c-brand-soft: rgba(245, 158, 11, 0.16);
|
||||||
|
--vp-c-bg: #111315;
|
||||||
|
--vp-c-bg-alt: #191c1f;
|
||||||
|
--vp-c-bg-elv: #202428;
|
||||||
|
--vp-c-bg-soft: #1c2023;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-padding-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height) + 24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-banner {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0 0 auto;
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 9px;
|
||||||
|
height: var(--vp-layout-top-height);
|
||||||
|
padding: 0 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: 1px solid rgba(120, 53, 15, 0.2);
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #78350f;
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 650;
|
||||||
|
letter-spacing: 0.055em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .draft-banner {
|
||||||
|
border-bottom-color: rgba(251, 191, 36, 0.22);
|
||||||
|
background: #2b2114;
|
||||||
|
color: #fde68a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-banner__mark {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #d97706;
|
||||||
|
box-shadow: 0 0 0 4px rgba(217, 119, 6, 0.13);
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPNavBarTitle .logo {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-set-header {
|
||||||
|
display: grid;
|
||||||
|
gap: 7px;
|
||||||
|
margin: 0 0 22px;
|
||||||
|
padding: 0 0 18px;
|
||||||
|
border-bottom: 1px solid var(--vp-c-divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-set-header__back {
|
||||||
|
width: fit-content;
|
||||||
|
color: var(--vp-c-text-3);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 650;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-set-header__back::before {
|
||||||
|
content: '←';
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-set-header__title {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-set-header__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-set-header__meta span {
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--vp-c-brand-soft);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.045em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHome {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(120, 113, 108, 0.07) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(120, 113, 108, 0.07) 1px, transparent 1px);
|
||||||
|
background-position: center top;
|
||||||
|
background-size: 40px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .name,
|
||||||
|
.VPHero .text {
|
||||||
|
letter-spacing: -0.045em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .tagline {
|
||||||
|
max-width: 660px;
|
||||||
|
font-size: 19px;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPFeature {
|
||||||
|
border-color: rgba(120, 113, 108, 0.2) !important;
|
||||||
|
background: color-mix(in srgb, var(--vp-c-bg-soft) 84%, transparent) !important;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h1,
|
||||||
|
.vp-doc h2,
|
||||||
|
.vp-doc h3 {
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h1 {
|
||||||
|
font-size: clamp(2rem, 4vw, 2.65rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc table {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule {
|
||||||
|
position: relative;
|
||||||
|
margin: 18px 0;
|
||||||
|
padding: 15px 18px 15px 20px;
|
||||||
|
scroll-margin-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height) + 24px);
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
|
border-left: 3px solid var(--vp-c-brand-2);
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
background: var(--vp-c-bg-soft);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule strong:first-child {
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
font-size: 0.89em;
|
||||||
|
letter-spacing: -0.015em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule__permalink {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
margin: 0 2px 0 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
font-family: var(--vp-font-family-mono);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
opacity: 0;
|
||||||
|
transition: background-color 0.15s, opacity 0.15s;
|
||||||
|
vertical-align: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule:hover .slm-rule__permalink,
|
||||||
|
.vp-doc .slm-rule__permalink:focus-visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule__permalink:hover,
|
||||||
|
.vp-doc .slm-rule__permalink:focus-visible {
|
||||||
|
background: var(--vp-c-brand-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule--prohibited {
|
||||||
|
border-left-color: #dc2626;
|
||||||
|
background: rgba(220, 38, 38, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule--recommended {
|
||||||
|
border-left-color: #2563eb;
|
||||||
|
background: rgba(37, 99, 235, 0.055);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule--optional {
|
||||||
|
border-left-color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule:target {
|
||||||
|
outline: 3px solid var(--vp-c-brand-soft);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.draft-banner {
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-inline: 12px;
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPHero .tagline {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule {
|
||||||
|
margin-inline: -8px;
|
||||||
|
padding: 13px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc .slm-rule__permalink {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
site/README.md
Normal file
23
site/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# SLM Design 2.0 Draft
|
||||||
|
|
||||||
|
`site/` содержит VitePress-конфигурацию, тему и статические ресурсы сайта SLM Design.
|
||||||
|
|
||||||
|
Новый publishable corpus находится в `docs/`. Действующая legacy-документация и reference текущего skill находятся в `old-docs/` до отдельного решения о принятии новой спецификации.
|
||||||
|
|
||||||
|
## Точка входа
|
||||||
|
|
||||||
|
[SLM Design Specification](../docs/ru/specification/index.md)
|
||||||
|
|
||||||
|
Specification определяет base SLM и два независимых [architecture modes](../docs/ru/specification/architecture-modes.md): `SLM Advanced` и `SLM Pro`. Каждый mode является отдельным overlay непосредственно над base SLM.
|
||||||
|
|
||||||
|
## Границы текущего этапа
|
||||||
|
|
||||||
|
На этом этапе в `docs/ru/specification/` размещается только нормативная русская спецификация. Английский раздел зарезервирован под будущий перевод. Учебные материалы, руководства, примеры, справочники и agent skill будут проектироваться после стабилизации правил.
|
||||||
|
|
||||||
|
## Локальный запуск
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run docs:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Production build создаётся командой `npm run docs:build`.
|
||||||
4
site/public/logo.svg
Normal file
4
site/public/logo.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" role="img" aria-label="SLM Design">
|
||||||
|
<rect width="40" height="40" rx="10" fill="#111315"/>
|
||||||
|
<path d="M10 11h20v5H16v3h11v5H16v5h14" fill="none" stroke="#f59e0b" stroke-width="4" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 273 B |
5
site/vite.config.mts
Normal file
5
site/vite.config.mts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
publicDir: fileURLToPath(new URL('./public', import.meta.url)),
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# SLM Design
|
# SLM Design
|
||||||
|
|
||||||
`docs/` - файлы документации по SLM-архитектуре.
|
Этот каталог содержит действующую legacy-документацию по SLM-архитектуре.
|
||||||
|
|
||||||
Используй эту документацию как источник истины для задач по SLM Design: слоям, модулям, сегментам, публичному API, business factory, composition modules и монорепозиториям.
|
Используй эту документацию как источник истины для задач по SLM Design: слоям, модулям, сегментам, публичному API, business factory, composition modules и монорепозиториям.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# SLM Design
|
# SLM Design
|
||||||
|
|
||||||
<!-- include: ../../docs/canons/decision-process.md -->
|
<!-- include: ../../old-docs/canons/decision-process.md -->
|
||||||
|
|
||||||
<!-- include: ../../docs/canons/business-runtime-boundary.md -->
|
<!-- include: ../../old-docs/canons/business-runtime-boundary.md -->
|
||||||
|
|
||||||
<!-- include: ../../docs/canons/validation.md -->
|
<!-- include: ../../old-docs/canons/validation.md -->
|
||||||
|
|||||||
Reference in New Issue
Block a user