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/
|
||||
site/.vitepress/cache/
|
||||
site/.vitepress/dist/
|
||||
.DS_Store
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
## Структура
|
||||
|
||||
- `docs/` — исходная документация и спецификация SLM Design.
|
||||
- `docs/` — новый нормативный корпус и материалы сайта.
|
||||
- `old-docs/` — действующая legacy-документация для текущего skill.
|
||||
- `site/` — VitePress-конфигурация, тема и статические ресурсы.
|
||||
- `src-skills/` — исходники agent skills.
|
||||
- `skills/` — собранные skills для установки через `npx skills`.
|
||||
|
||||
@@ -17,7 +19,7 @@ npm run build
|
||||
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/
|
||||
```
|
||||
|
||||
**SLM-ARCH-001 - ОБЯЗАН.** Base SLM-приложение должно разделять код по ответственности между слоями `app`, `compositions`, `infra`, `ui` и `shared`.
|
||||
**SLM-BASE-ARCH-001 - ОБЯЗАН.** Base SLM-приложение должно разделять код по ответственности между слоями `app`, `compositions`, `infra`, `ui` и `shared`.
|
||||
|
||||
Не каждый слой обязан содержать код в минимальном приложении. Пустые папки и speculative scaffolding не требуются.
|
||||
|
||||
@@ -42,11 +42,11 @@ shared -/-> остальные 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
|
||||
```
|
||||
|
||||
**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
|
||||
|
||||
@@ -25,11 +25,11 @@ SLM + Advanced
|
||||
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 и количество команд разработки. Фиксированные числовые пороги не устанавливаются.
|
||||
|
||||
@@ -44,7 +44,7 @@ SLM + Pro
|
||||
Base-правило имеет идентификатор вида:
|
||||
|
||||
```text
|
||||
SLM-AREA-NNN
|
||||
SLM-BASE-AREA-NNN
|
||||
```
|
||||
|
||||
Mode-specific правила имеют идентификаторы:
|
||||
@@ -54,15 +54,15 @@ SLM-ADV-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
|
||||
|
||||
@@ -76,6 +76,6 @@ SLM-PRO-AREA-NNN
|
||||
|
||||
## Изменение 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`.
|
||||
|
||||
Пока статус равен `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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
framework route
|
||||
→ 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.
|
||||
|
||||
Запрет другим 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`;
|
||||
- product Provider;
|
||||
@@ -36,17 +36,17 @@ compositions/
|
||||
|
||||
## 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 у себя.
|
||||
|
||||
## 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
|
||||
|
||||
**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;
|
||||
- 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
|
||||
|
||||
**SLM-CMP-008 - ОБЯЗАН.** Page-local presentation state принадлежит минимальной composition, охватывающей всех его consumers.
|
||||
**SLM-BASE-CMP-008 - ОБЯЗАН.** Page-local presentation state принадлежит минимальной composition, охватывающей всех его consumers.
|
||||
|
||||
Примеры page-local state:
|
||||
|
||||
@@ -70,15 +70,15 @@ Composition может использовать public API `infra` для extern
|
||||
- presentation filters;
|
||||
- состояние раскрытия 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
|
||||
|
||||
**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
|
||||
|
||||
@@ -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.
|
||||
|
||||
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 без текущей ответственности.
|
||||
|
||||
@@ -76,7 +76,7 @@ Domain может хранить файлы в корне и использов
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -93,7 +93,7 @@ domain -> domain | infra | ui | shared
|
||||
|
||||
**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 остаётся ацикличным.
|
||||
|
||||
@@ -113,7 +113,7 @@ composition
|
||||
|
||||
**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
|
||||
|
||||
@@ -49,11 +49,11 @@ Base dependency direction для остальных слоёв сохраняе
|
||||
|
||||
## Изменение 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.
|
||||
|
||||
**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
|
||||
|
||||
@@ -69,7 +69,7 @@ Server technical inputs ограничены request/framework data, server envi
|
||||
|
||||
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
|
||||
module import
|
||||
@@ -100,6 +100,6 @@ explicit start
|
||||
|
||||
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.
|
||||
@@ -70,7 +70,7 @@ Domain React UI может:
|
||||
|
||||
**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
|
||||
|
||||
@@ -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.
|
||||
|
||||
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-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.
|
||||
|
||||
@@ -128,7 +128,7 @@ Stateless logic API также является DomainRuntime, если он с
|
||||
|
||||
**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
|
||||
|
||||
@@ -48,7 +48,7 @@ domains -> согласно внутренним Pro zones
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -43,11 +43,11 @@ Segment группирует внутренние файлы module по уст
|
||||
|
||||
`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
|
||||
|
||||
**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
|
||||
|
||||
`docs/` - файлы документации по SLM-архитектуре.
|
||||
Этот каталог содержит действующую legacy-документацию по SLM-архитектуре.
|
||||
|
||||
Используй эту документацию как источник истины для задач по 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": {
|
||||
"build": "npm run build:skill",
|
||||
"build:skill": "node scripts/build-skill.mjs",
|
||||
"check": "npm run build && npm run check:skill",
|
||||
"check:skill": "node scripts/check-skill.mjs"
|
||||
"check": "npm run build && npm run check:skill && npm run check:docs-all",
|
||||
"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": {
|
||||
"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 sourceDir = path.join(repoRoot, 'src-skills', skillConfig.name);
|
||||
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 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)}`);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(docsDir)) {
|
||||
throw new Error('Documentation directory not found: docs');
|
||||
if (!fs.existsSync(legacyDocsDir)) {
|
||||
throw new Error('Legacy documentation directory not found: old-docs');
|
||||
}
|
||||
|
||||
const source = fs.readFileSync(sourcePath, 'utf8');
|
||||
@@ -65,6 +65,6 @@ const output = [
|
||||
fs.rmSync(outputDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
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')));
|
||||
|
||||
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
|
||||
|
||||
`docs/` - файлы документации по SLM-архитектуре.
|
||||
Этот каталог содержит действующую legacy-документацию по SLM-архитектуре.
|
||||
|
||||
Используй эту документацию как источник истины для задач по SLM Design: слоям, модулям, сегментам, публичному API, business factory, composition modules и монорепозиториям.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 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