@rt-tools/agent-kit 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -9
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +17 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +78 -0
- package/assets/hooks/browser-guard-require-select.sh +53 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +42 -0
- package/assets/hooks/dev-server-guard.sh +113 -0
- package/assets/hooks/docs-guard.sh +96 -0
- package/assets/hooks/git-guard-delivery.sh +110 -0
- package/assets/hooks/git-guard-main.sh +72 -0
- package/assets/hooks/git-guard-push-tests.sh +73 -0
- package/assets/hooks/lint-after-edit.sh +94 -0
- package/assets/hooks/qa-dataid-guard.sh +81 -0
- package/assets/hooks/reuse-first-guard.sh +83 -0
- package/assets/hooks/skill-gate-rearm.sh +22 -0
- package/assets/hooks/skill-gate.sh +68 -0
- package/assets/hooks/skill-loaded.sh +20 -0
- package/assets/hooks/sql-guard.sh +129 -0
- package/assets/laws/access.md +3 -12
- package/assets/laws/admin-lists.md +9 -21
- package/assets/laws/admin-navigation.md +3 -15
- package/assets/laws/code-structure.md +5 -16
- package/assets/laws/delivery.md +2 -16
- package/assets/laws/entity-editing.md +7 -20
- package/assets/laws/entity-models.md +10 -17
- package/assets/laws/frontend-application.md +3 -14
- package/assets/laws/lib-imports.md +0 -13
- package/assets/laws/locales.md +2 -11
- package/assets/laws/project-documentation.md +7 -20
- package/assets/laws/reuse-first.md +0 -9
- package/assets/laws/search-visibility.md +0 -13
- package/assets/laws/shared-code.md +0 -13
- package/assets/laws/verifiability.md +0 -13
- package/assets/patterns/angular-patterns-state.md +94 -0
- package/assets/patterns/api-layer-pair.md +78 -0
- package/assets/patterns/browser-verification-measure.md +83 -0
- package/assets/patterns/browser-verification-stand.md +79 -0
- package/assets/patterns/component-structure-new.md +98 -0
- package/assets/patterns/doc-style-sweep.md +100 -0
- package/assets/patterns/doc-style-write.md +106 -0
- package/assets/patterns/git-workflow-commit.md +175 -0
- package/assets/patterns/git-workflow-merge.md +82 -0
- package/assets/patterns/git-workflow-migration.md +58 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +77 -0
- package/assets/patterns/lib-layers-new.md +70 -0
- package/assets/patterns/permissions-procedure.md +69 -0
- package/assets/patterns/platform-access-di.md +70 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +92 -0
- package/assets/patterns/seo-verify.md +64 -0
- package/assets/patterns/shared-code-new.md +80 -0
- package/assets/patterns/spec-driven-domain.md +100 -0
- package/assets/patterns/spec-driven-rule.md +112 -0
- package/assets/patterns/styling-bem-component.md +77 -0
- package/assets/patterns/styling-bem-layout.md +67 -0
- package/assets/patterns/testing-e2e.md +90 -0
- package/assets/patterns/testing-unit.md +93 -0
- package/assets/patterns/translations-key.md +51 -0
- package/assets/patterns/ts-procedure.md +66 -0
- package/assets/rules/angular-patterns.md +52 -0
- package/assets/rules/api-layer.md +53 -0
- package/assets/rules/browser-verification.md +69 -0
- package/assets/rules/component-structure.md +48 -0
- package/assets/rules/doc-style.md +61 -0
- package/assets/rules/git-workflow.md +106 -0
- package/assets/rules/lib-layers.md +54 -0
- package/assets/rules/permissions.md +52 -0
- package/assets/rules/platform-access.md +49 -0
- package/assets/rules/reuse-first.md +69 -0
- package/assets/rules/seo.md +50 -0
- package/assets/rules/shared-code.md +45 -0
- package/assets/rules/spec-driven.md +89 -0
- package/assets/rules/styling-bem.md +59 -0
- package/assets/rules/testing.md +69 -0
- package/assets/rules/translations.md +52 -0
- package/assets/rules/typescript-conventions.md +46 -0
- package/assets/templates/gate-map.sh +37 -0
- package/assets/templates/implementation.md +38 -0
- package/assets/templates/pattern.md +4 -0
- package/assets/templates/project.sh +41 -0
- package/assets/templates/rule.md +12 -23
- package/bin/agent-kit.d.ts +1 -1
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +63 -7
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +9 -0
- package/bin/prompt.d.ts.map +1 -0
- package/bin/prompt.js +57 -0
- package/bin/prompt.js.map +1 -0
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +10 -2
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +24 -28
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +44 -0
- package/lib/catalog.d.ts.map +1 -0
- package/lib/catalog.js +86 -0
- package/lib/catalog.js.map +1 -0
- package/lib/commands.d.ts +13 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +106 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +53 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +33 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +29 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +43 -6
- package/lib/config.js.map +1 -1
- package/lib/picker.d.ts +47 -0
- package/lib/picker.d.ts.map +1 -0
- package/lib/picker.js +112 -0
- package/lib/picker.js.map +1 -0
- package/lib/stamp.d.ts +2 -5
- package/lib/stamp.d.ts.map +1 -1
- package/lib/stamp.js +25 -10
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +3 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +20 -1
- package/lib/sync.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.3.0.tgz +0 -0
- package/rt-tools-agent-kit-0.1.0.tgz +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: platform-access-di
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: platform-access
|
|
5
|
+
description: Паттерн правила platform-access. Брать, когда в код заходит окно, документ или проверка среды — внедрение токенов, приведение к глобальной области, окно параметром в чистой функции, работа с разметкой после первой отрисовки. Не брать на серверной стороне.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Окно, документ и проверка среды
|
|
9
|
+
|
|
10
|
+
Паттерн правила `platform-access`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/frontend-application.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- В компонент, службу или директиву заходит окно, документ или проверка среды.
|
|
16
|
+
- Появляется работа с разметкой, которой нельзя случиться до первой отрисовки.
|
|
17
|
+
- Чистой функции нужен доступ к окну.
|
|
18
|
+
|
|
19
|
+
## Внедрение
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
@Injectable({ providedIn: 'root' })
|
|
23
|
+
export class SomeService {
|
|
24
|
+
readonly #document: Document = inject(DOCUMENT);
|
|
25
|
+
readonly #platform: PlatformService = inject(PlatformService);
|
|
26
|
+
readonly #window: Window = inject(WINDOW);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Когда нужен тип глобальной области
|
|
31
|
+
|
|
32
|
+
Интерфейс окна не описывает глобальные конструкторы и пространства имён — наблюдателей
|
|
33
|
+
пересечения и размера, объекты внешних карт. Токен отдаёт тот же самый объект, поэтому тип
|
|
34
|
+
уточняется приведением, и рядом ставится комментарий с причиной:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Конструкторы наблюдателей объявлены на глобальной области, а не на интерфейсе окна —
|
|
38
|
+
// токен отдаёт тот же объект, тип лишь уточняется.
|
|
39
|
+
readonly #window: Window & typeof globalThis = inject(WINDOW) as Window & typeof globalThis;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Чистая функция принимает окно параметром
|
|
43
|
+
|
|
44
|
+
В файлах чистой логики внедрения нет, и глобал внутрь не тянется:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
export function mapsReady(windowRef: Window & typeof globalThis): boolean {
|
|
48
|
+
return typeof windowRef.maps?.importLibrary === 'function';
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Внедряет его вызывающий компонент.
|
|
53
|
+
|
|
54
|
+
## Проверка среды и первая отрисовка
|
|
55
|
+
|
|
56
|
+
Среда спрашивается у службы платформы, а работа с разметкой уходит в крючок первой отрисовки.
|
|
57
|
+
Проверка «глобал определён» не годится: она верна случайно и ломается на первой же среде, где
|
|
58
|
+
глобал подставлен.
|
|
59
|
+
|
|
60
|
+
## Частые промахи
|
|
61
|
+
|
|
62
|
+
- **Проверка среды прямым обращением к признаку платформы** вместо службы.
|
|
63
|
+
- **Проверка среды вокруг чтения и записи в хранилище:** служба хранилища и так уходит в память
|
|
64
|
+
вне браузера, и такое условие — мёртвый код.
|
|
65
|
+
- **Окно полем класса в службе, обязанной работать без разметки вовсе:** там оно берётся внутри
|
|
66
|
+
метода под проверкой среды.
|
|
67
|
+
- **Правка добавила окно в службу, создающуюся на подъёме, а проверили одной сборкой:** падение
|
|
68
|
+
видно только на поднятом сервере отдачи страниц.
|
|
69
|
+
- **Приведение без комментария:** в переводчиках моделей приведение запрещено, и строка
|
|
70
|
+
читается как нарушение.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reuse-first-extend
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: reuse-first
|
|
5
|
+
description: Паттерн правила reuse-first. Брать, когда готового в источнике вида или в базовом классе не хватило — что проверить перед тем, как писать своё, как расширить готовое, как объявить разовое отступление маркером и когда его снимать.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Готового не хватило
|
|
9
|
+
|
|
10
|
+
Паттерн правила `reuse-first`. Что при этом должно быть верно — закон `{{lawsDir}}/reuse-first.md`.
|
|
11
|
+
|
|
12
|
+
## Когда брать
|
|
13
|
+
|
|
14
|
+
Готовый компонент или базовый класс не покрывает случай, и рука тянется написать своё рядом.
|
|
15
|
+
|
|
16
|
+
## Сначала — проверить, что действительно не хватает
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
grep -c "export" <барель источника вида>
|
|
20
|
+
grep -rn "<похожий приём>" <каталоги кода> --include='*.html' | head
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Источник вида большой, и большинства его компонентов нет ни в одной таблице. Соседний домен
|
|
24
|
+
читается целиком: приём, который кажется новым, обычно уже написан — и переименованный при
|
|
25
|
+
переносе он перестаёт узнаваться.
|
|
26
|
+
|
|
27
|
+
Частые подмены, которые находятся при чтении: готовый мерцающий заполнитель вместо своего,
|
|
28
|
+
готовый диалог вместо своей вуали, готовое сообщение вместо своей области оповещения.
|
|
29
|
+
|
|
30
|
+
## Расширять, а не клонировать
|
|
31
|
+
|
|
32
|
+
Недостающий вариант заводится **в источнике вида или в базовом классе**, и его видят остальные
|
|
33
|
+
экраны.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
✗ <домен>/ui/my-dialog/ клон «почти как готовый»
|
|
37
|
+
✓ <источник вида>/dialog/ новый вариант у готового
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Клон, написанный рядом, забирает правки на себя и расходится с оригиналом с первой же.
|
|
41
|
+
Переименованный файл и «похожий, но свой» компонент — то же отступление, только необъявленное.
|
|
42
|
+
|
|
43
|
+
## Свой примитив — только с одобрения владельца
|
|
44
|
+
|
|
45
|
+
Спрашивается до того, как написан первый файл. То же относится к своей основе и к своему
|
|
46
|
+
оформлению на месте.
|
|
47
|
+
|
|
48
|
+
## Разовое отступление объявляется маркером
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<!-- <маркер>: в источнике вида нет поля с маской телефона, заведено задачей -->
|
|
52
|
+
<input type="tel" qa-dataid="phone-input" />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Маркер ставится в той же строке и объясняет, **чего именно нет в готовом**. «Эти строки были
|
|
56
|
+
здесь раньше» причиной не считается: гард вычёркивает из проверяемого текста то, что уже лежит
|
|
57
|
+
в файле, поэтому отказ означает новый текст.
|
|
58
|
+
|
|
59
|
+
Сверка идёт без отступов — при переезде блок меняет отступ, оставаясь тем же кодом.
|
|
60
|
+
|
|
61
|
+
## Снятие отступления
|
|
62
|
+
|
|
63
|
+
Когда недостающее появилось в готовом, маркер снимается вместе с обходом. Комментарий,
|
|
64
|
+
оправдывающий отклонение, держит это отклонение на себе: пока объяснение выглядит убедительно,
|
|
65
|
+
его не трогают.
|
|
66
|
+
|
|
67
|
+
## Частые промахи
|
|
68
|
+
|
|
69
|
+
- Своё написано до чтения источника вида и соседнего домена.
|
|
70
|
+
- Клон рядом вместо нового варианта у готового.
|
|
71
|
+
- Маркер поставлен без объяснения, чего не хватает.
|
|
72
|
+
- Маркер оставлен после того, как готовое появилось.
|
|
73
|
+
- Своя основа спроектирована заново вместо переноса образца владельца дословно.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: seo-page
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: seo
|
|
5
|
+
description: Паттерн правила seo. Брать, когда правится разметка публичной страницы, заводится новый маршрут или новая страница должна попасть в карту сайта — вызов общей службы тегов, ветки локалей в маршрутах, запись в карте сайта. Не брать для проверки отданной разметки — это паттерн seo-verify.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Разметка публичной страницы
|
|
9
|
+
|
|
10
|
+
Паттерн правила `seo`. Что при этом должно быть верно — закон `{{lawsDir}}/search-visibility.md`.
|
|
11
|
+
|
|
12
|
+
## Когда брать
|
|
13
|
+
|
|
14
|
+
- Правится шаблон публичной страницы, её заголовок, описание или картинка для соцсетей.
|
|
15
|
+
- Заводится новый публичный маршрут.
|
|
16
|
+
- Появилась страница, которая должна попасть в карту сайта.
|
|
17
|
+
|
|
18
|
+
## Разметку ставит служба, а не шаблон
|
|
19
|
+
|
|
20
|
+
Страница собирает данные и одним вызовом отдаёт их общей службе тегов. Своих тегов в шаблоне не
|
|
21
|
+
заводить: они не помечены и потому не переписываются при переходе — тег от прошлой страницы
|
|
22
|
+
переживёт следующую.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
readonly #seo: PageSeoService = inject(PageSeoService);
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
effect((): void => this.#applySeo());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#applySeo(): void {
|
|
32
|
+
const entity: IEntity.State | null = this.entity();
|
|
33
|
+
if (!entity) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.#seo.apply({
|
|
38
|
+
entity,
|
|
39
|
+
pageTitle: this.title(),
|
|
40
|
+
description: entity.shortDescription || this.#descriptionFallback(),
|
|
41
|
+
ogImageUrl: this.cover() ? ogUrl(this.cover()) : this.#brandImage(),
|
|
42
|
+
locale: this.#localeId,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Вызов идёт из эффекта, а не из конструктора напрямую: запись приходит реактивным значением, и
|
|
48
|
+
на первом кадре её ещё нет. Ранний выход по пустой записи обязателен — без него разметка встала
|
|
49
|
+
бы на пустых значениях и второй раз уже не переписалась бы.
|
|
50
|
+
|
|
51
|
+
У картинки есть запасной вариант: запись без обложки отдаёт брендовый кадр того же формата,
|
|
52
|
+
иначе превью в соцсети пустует.
|
|
53
|
+
|
|
54
|
+
## Новый маршрут заводится веткой на каждую локаль
|
|
55
|
+
|
|
56
|
+
Ветки собираются из списка локалей; локаль по умолчанию своей ветки не имеет — она отдаётся из
|
|
57
|
+
корня.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
export const appRoutes: Route[] = [
|
|
61
|
+
...LOCALE_CODES.filter((code: ELocale): boolean => code !== DEFAULT_LOCALE).map((code: ELocale): Route => ({
|
|
62
|
+
path: code,
|
|
63
|
+
children: publicRoutes,
|
|
64
|
+
})),
|
|
65
|
+
...publicRoutes,
|
|
66
|
+
];
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Новый путь дописывается в общий набор — тогда он появляется во всех ветках сразу. Ветки
|
|
70
|
+
перечислены явными кодами, а не параметром: иначе первый же сегмент адреса был бы принят за
|
|
71
|
+
язык.
|
|
72
|
+
|
|
73
|
+
## Страница попадает в карту сайта явно
|
|
74
|
+
|
|
75
|
+
Карта строится из записей, а не из маршрутов, и новая страница сама туда не попадёт. Правка
|
|
76
|
+
идёт вместе со спекой на сборщик карты.
|
|
77
|
+
|
|
78
|
+
## Тексты идут из словарей
|
|
79
|
+
|
|
80
|
+
Заголовок и описание собираются из переведённых значений и заводятся во всех локалях. Без
|
|
81
|
+
перевода они уезжают в выдачу на одном языке, и заметно это только в чужой локали.
|
|
82
|
+
|
|
83
|
+
## Частые промахи
|
|
84
|
+
|
|
85
|
+
- **Тег в шаблоне вместо вызова службы** — он не помечен и переживёт переход.
|
|
86
|
+
- **Вызов без раннего выхода по пустой записи** — разметка встаёт на пустых значениях.
|
|
87
|
+
- **Новый путь дописан мимо общего набора** — работает только в локали по умолчанию, остальные
|
|
88
|
+
адреса отдают отказ.
|
|
89
|
+
- **Свой разбор адреса вместо общего** — теряется всё после второго знака вопроса, и источник
|
|
90
|
+
обращения считается неверно.
|
|
91
|
+
- **Правка разметки без проверки на прод-сборке:** на сервере разработки теги ставит другой
|
|
92
|
+
путь. Проверка описана паттерном `seo-verify`.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: seo-verify
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: seo
|
|
5
|
+
description: Паттерн правила seo. Брать после любой правки, задевающей публичную разметку, теги головы документа или маршруты — сборка, поднятие сервера отдачи страниц, проверка отданной разметки по всем локалям, карта сайта, кэшируемость перенаправления. Не брать для самой правки разметки — это паттерн seo-page.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Проверка разметки на прод-сборке
|
|
9
|
+
|
|
10
|
+
Паттерн правила `seo`. Что при этом должно быть верно — закон `{{lawsDir}}/search-visibility.md`.
|
|
11
|
+
|
|
12
|
+
## Когда брать
|
|
13
|
+
|
|
14
|
+
После любой правки, задевающей публичную разметку, теги головы документа, маршруты, серверную
|
|
15
|
+
точку входа, правила обхода или конфиг прокси.
|
|
16
|
+
|
|
17
|
+
## Сервер разработки здесь не показатель
|
|
18
|
+
|
|
19
|
+
Теги ставятся при отдаче страницы сервером. В режиме разработки этот путь другой, поэтому
|
|
20
|
+
проверка идёт на собранном приложении с поднятым сервером отдачи страниц.
|
|
21
|
+
|
|
22
|
+
Сервер отдачи страниц отвечает отказом на чужое имя хоста — запросы идут с явным заголовком
|
|
23
|
+
хоста.
|
|
24
|
+
|
|
25
|
+
## Что смотреть в отданной разметке
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
for locale in "" <остальные локали>; do
|
|
29
|
+
printf '%-10s ' "${locale:-<по умолчанию>}"
|
|
30
|
+
curl -s -H "Host: localhost" "http://localhost:<порт>/${locale}<путь>" \
|
|
31
|
+
| grep -c -E '<title>|name="description"|property="og:|rel="canonical"|hreflang=|application/ld\+json'
|
|
32
|
+
done
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
В ответе каждой локали должны быть заголовок, описание, набор тегов соцсетей, канонический
|
|
36
|
+
адрес, полный набор языковых ссылок вместе со ссылкой по умолчанию и блок структурированных
|
|
37
|
+
данных.
|
|
38
|
+
|
|
39
|
+
Отдельно проверяется, что канонический адрес ведёт на **свой** язык, а не на локаль по
|
|
40
|
+
умолчанию.
|
|
41
|
+
|
|
42
|
+
## Карта сайта
|
|
43
|
+
|
|
44
|
+
Карта строится из живых данных, а не из файла. Пустой ответ означает, что не поднялся запрос за
|
|
45
|
+
записями, — это отказ, а не «записей нет».
|
|
46
|
+
|
|
47
|
+
## Кэшируемость перенаправления
|
|
48
|
+
|
|
49
|
+
Проверяется только на стенде с настоящим конфигом прокси: голый сервер отдачи страниц отдаёт
|
|
50
|
+
заголовки, но не показывает, попадёт ли ответ в кэш.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
curl -sI http://<стенд>/<прежний адрес> | grep -i -E 'HTTP/|location|cache-control|x-cache'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Ответ обязан быть постоянным перенаправлением, нести срок жизни и попадать в кэш.
|
|
57
|
+
|
|
58
|
+
## Частые промахи
|
|
59
|
+
|
|
60
|
+
- **Проверка на сервере разработки:** теги ставит другой путь, и результат ничего не говорит о
|
|
61
|
+
проде.
|
|
62
|
+
- **Проверена одна локаль:** ветка маршрутов, забытая для остальных, отдаёт отказ и поисковику,
|
|
63
|
+
и читателю.
|
|
64
|
+
- **Заголовки смотрели на голом сервере:** кэш и перенаправления живут в прокси.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shared-code-new
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: shared-code
|
|
5
|
+
description: Паттерн правила shared-code. Брать, когда заводится новое число-настройка, общая функция или общий тип, который должны одинаково понимать все приложения — куда класть, как объявить, как сверить строку с набором и как убедиться, что копия не осталась на старом месте.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Новое общее заводится так
|
|
9
|
+
|
|
10
|
+
Паттерн правила `shared-code`. Что при этом должно быть верно — закон `{{lawsDir}}/shared-code.md`.
|
|
11
|
+
|
|
12
|
+
## Когда брать
|
|
13
|
+
|
|
14
|
+
- Появилось число-настройка: предел, размер, длительность.
|
|
15
|
+
- Появилась функция без каркаса, нужная обеим сторонам.
|
|
16
|
+
- Значение приходит строкой и должно быть сверено с конечным набором.
|
|
17
|
+
|
|
18
|
+
## Куда класть
|
|
19
|
+
|
|
20
|
+
| Что | Куда |
|
|
21
|
+
| -------------------------------------------- | ------------------------------------------- |
|
|
22
|
+
| число или функция без каркаса | общая либа утилит, файл по предмету |
|
|
23
|
+
| готовый тип или набор значений | берётся из общего пакета, не переписывается |
|
|
24
|
+
| токен внедрения, общий двум фронтовым семьям | либа платформы |
|
|
25
|
+
|
|
26
|
+
Файл выбирается по предмету, а не по роду («константы», «функции»): предметный файл читается
|
|
27
|
+
целиком, свалка по роду — никогда. Дальше — строка в барель.
|
|
28
|
+
|
|
29
|
+
## Число объявляется один раз и без довода
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export const DEFAULT_PAGE_SIZE: number = 20;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
✗ export function listPageOf(query: IListQuery | undefined, defaultPageSize: number): IListPage
|
|
37
|
+
✓ export function listPageOf(query: IListQuery | undefined): IListPage
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Пока умолчание передаётся доводом, домен вправе назвать своё число — и называет, расходясь с
|
|
41
|
+
соседним на единицу, которую никто не заметит.
|
|
42
|
+
|
|
43
|
+
## Строка сверяется с набором, а не приводится к типу
|
|
44
|
+
|
|
45
|
+
Приведение принимает любую строку. Сверку делает общая функция, а что делать с промахом, решает
|
|
46
|
+
вызывающий:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const operator: TFilterOperator | null = listFilterOperatorOf(filter.operatorType);
|
|
50
|
+
if (!operator) {
|
|
51
|
+
throw new RequestError(`filter operator is required: ${filter.propertyName}`);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const direction: TListSortOrder = listSortOrderOf(rawDirection) ?? LIST_SORT_ORDER_ENUM.ASC;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Сервер отбивает запрос, экран берёт умолчание. Общий переводчик здесь не годится: он подал бы
|
|
60
|
+
промах умолчанием, и клиент получил бы отбор, которого не просил.
|
|
61
|
+
|
|
62
|
+
Помощник приведения типов для этого тоже не годится — значение вне набора он пишет в журнал и
|
|
63
|
+
возвращает строкой.
|
|
64
|
+
|
|
65
|
+
## Проверить, что копия не осталась
|
|
66
|
+
|
|
67
|
+
Проверка повторов падает на четырёх признаках: одно имя из двух либ, два перечисления с
|
|
68
|
+
одинаковым набором членов, число-настройка под одним именем в двух либах, перечисление,
|
|
69
|
+
повторяющее набор из общего пакета.
|
|
70
|
+
|
|
71
|
+
Накопленное лежит в списке исключений и отказом не считается. Список только сокращается: новая
|
|
72
|
+
строка в нём означает, что повтор завели уже после проверки.
|
|
73
|
+
|
|
74
|
+
## Частые промахи
|
|
75
|
+
|
|
76
|
+
- Своё перечисление с теми же членами, что уже есть в общем пакете, — копия, даже если имена
|
|
77
|
+
разошлись.
|
|
78
|
+
- Умолчание, переданное доводом, — домен назовёт своё число, и разъезд будет молчаливым.
|
|
79
|
+
- Ту же логику, написанную заново под другим именем, проверка не ловит.
|
|
80
|
+
- Строковая настройка и таблица соответствий не учитываются вовсе.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-driven-domain
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: spec-driven
|
|
5
|
+
description: Паттерн правила spec-driven. Брать при заведении или правке спека домена — раскладка файлов, обязательные разделы, форма правила и его привязки, форма сценария, порядок работы от спека к коду. Не брать для заведения закона, правила или паттерна — это паттерн spec-driven-rule.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Спек домена
|
|
9
|
+
|
|
10
|
+
Паттерн правила `spec-driven`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/project-documentation.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Заводится новый домен или фича, которой ещё нет.
|
|
16
|
+
- Правится контракт — спеки задетых доменов едут той же веткой.
|
|
17
|
+
- Замечено расхождение спека с кодом.
|
|
18
|
+
|
|
19
|
+
## Раскладка
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
<спеки>/<домен>/
|
|
23
|
+
spec.md — как домен работает
|
|
24
|
+
implementation.md — таблица «правило → файл:символ»
|
|
25
|
+
scenarios.md — сценарии SC-<ПРЕФИКС>-<НОМЕР>
|
|
26
|
+
proposed/<фича>/ — только то, чего ещё нет
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Обязательные разделы
|
|
30
|
+
|
|
31
|
+
Набор разделов задан заранее и сверяется дословно: зачем, терминология, правила, что не входит,
|
|
32
|
+
контракт с кодами отказов, данные, экраны и состояния, сквозные требования, решения. «Не
|
|
33
|
+
применимо» — законный ответ, отсутствие раздела — нет: сквозные требования вспоминаются
|
|
34
|
+
постфактум именно тогда, когда для них не заведено места.
|
|
35
|
+
|
|
36
|
+
Шапка несёт статус, префикс сценариев, зависимости от других доменов, строку с законами,
|
|
37
|
+
которые домен применяет, и строку с корнями либ, чьи обработчики он обслуживает.
|
|
38
|
+
|
|
39
|
+
```markdown
|
|
40
|
+
**Зависимости:** `<домен>` (что берётся), `<домен>` (что берётся)
|
|
41
|
+
**Законы:** `access`, `locales`, `shared-code`
|
|
42
|
+
**Обработчики:** `<корень либы>`
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Закон, названный где-нибудь в тексте спека, обязан стоять в этой строке: связь сверяется в обе
|
|
46
|
+
стороны.
|
|
47
|
+
|
|
48
|
+
## Правило и его привязка
|
|
49
|
+
|
|
50
|
+
Правило формулируется так, чтобы его можно было нарушить, и начинается с жирной фразы:
|
|
51
|
+
|
|
52
|
+
```markdown
|
|
53
|
+
- **Применяется одна максимальная скидка.** Сложение скидок даёт цену ниже себестоимости.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Привязка живёт в `implementation.md` рядом, ключ связи — сам текст правила:
|
|
57
|
+
|
|
58
|
+
```markdown
|
|
59
|
+
| Правило | Где исполняется |
|
|
60
|
+
| ------------------------------------- | ----------------- |
|
|
61
|
+
| Применяется одна максимальная скидка. | `<путь>:<символ>` |
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Правило, которому места в коде не нашлось, — намерение: ему место в открытых вопросах, а не
|
|
65
|
+
формальный якорь.
|
|
66
|
+
|
|
67
|
+
## Сценарий
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
### SC-<ПРЕФИКС>-19 — подтверждение на занятые даты отбивается
|
|
71
|
+
|
|
72
|
+
Дано у записи есть подтверждённая соседняя на пересекающиеся даты
|
|
73
|
+
Когда владелец подтверждает заявку
|
|
74
|
+
Тогда отказ подаётся владельцу как занятые даты, а не как ошибка хранилища
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Идентификатор ставится в начало заголовка теста, через тире. Сценарий без теста помечается
|
|
78
|
+
отметкой с причиной, сценарий с неполным тестом — отметкой о частичном покрытии.
|
|
79
|
+
|
|
80
|
+
## Порядок работы
|
|
81
|
+
|
|
82
|
+
1. Задача заводится сценариями: что станет верно, когда работа закончится.
|
|
83
|
+
2. Спек домена правится **до** кода.
|
|
84
|
+
3. Код пишется под сценарии, тесты называются их идентификаторами.
|
|
85
|
+
4. Проверка спеков — до пуша.
|
|
86
|
+
5. Приёмка идёт по сценариям, а не по пересказу правки.
|
|
87
|
+
|
|
88
|
+
## Частые промахи
|
|
89
|
+
|
|
90
|
+
- **Список шагов в спеке:** шаги — артефакт сессии, им место в ветке или в описании PR.
|
|
91
|
+
- **Скопированная из контракта таблица полей:** источник один, а компилируется из двух только
|
|
92
|
+
одна.
|
|
93
|
+
- **Колонки и индексы в спеке:** они в схеме хранилища, а в спеке остаётся правило, которое
|
|
94
|
+
ограничение выражает.
|
|
95
|
+
- **Место, где правило исполняется, внутри текста правила:** оно меняется при первом же
|
|
96
|
+
переносе, и для него заведён отдельный файл.
|
|
97
|
+
- **Отметка о непокрытом при существующем тесте** — отказ: долг закрыли, а отметку не сняли.
|
|
98
|
+
- **Закон, названный в тексте, но забытый в шапке:** по закону тогда не узнать, какие домены на
|
|
99
|
+
нём стоят.
|
|
100
|
+
- **Правка контракта без спеков задетых доменов** — гард документов отбивает такой коммит.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-driven-rule
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: spec-driven
|
|
5
|
+
description: Паттерн правила spec-driven. Брать при заведении или правке закона, правила или паттерна — готовые шапки, набор разделов каждого слоя, таблица привязки, признак того, что правило пора делить. Не брать для спека домена — это паттерн spec-driven-domain.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Закон, правило и паттерн
|
|
9
|
+
|
|
10
|
+
Паттерн правила `spec-driven`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/project-documentation.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- Правило приходится повторять ещё в двух местах — пора заводить закон.
|
|
16
|
+
- Заводится правило под уже существующий закон.
|
|
17
|
+
- Готовый код в правиле разросся — пора выносить паттерн.
|
|
18
|
+
|
|
19
|
+
## Закон
|
|
20
|
+
|
|
21
|
+
`{{lawsDir}}/<закон>.md`. О проекте не знает ничего: ни путей, ни имён файлов, ни привязок.
|
|
22
|
+
Признак закона: попытка положить статью в один спек заставляет повторить то же самое ещё в
|
|
23
|
+
двух.
|
|
24
|
+
|
|
25
|
+
Разделы: вводный абзац без заголовка, затем `## Статьи`. Обязательны статьи — их и сверяет
|
|
26
|
+
проверка.
|
|
27
|
+
|
|
28
|
+
Ни истории правок, ни доводов о том, почему когда-то выбрали так, в законе нет. Историю держит
|
|
29
|
+
система контроля версий, а довод с отвергнутой альтернативой — свойство работы, а не продукта:
|
|
30
|
+
ему место в «Ловушках» правила под этим законом, где и путям к файлам можно. Утверждение,
|
|
31
|
+
которое нельзя написать как «верно всегда», статьёй не становится вовсе.
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
# Поставка
|
|
35
|
+
|
|
36
|
+
Как правка доезжает до работающего приложения. …
|
|
37
|
+
|
|
38
|
+
## Статьи
|
|
39
|
+
|
|
40
|
+
- **Выкатывается образ того коммита, который выкатывают.** Умолчание «последний» отстаёт от
|
|
41
|
+
главной ветки, и приложение молча возвращается к прежней версии, продолжая отвечать.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Поведение кода законом не является: «стор отвечает булевым», «метод называется так-то» — этого
|
|
45
|
+
не видит ни гость, ни владелец. Граница простая: закон описывает то, что видно снаружи
|
|
46
|
+
приложения.
|
|
47
|
+
|
|
48
|
+
## Правило
|
|
49
|
+
|
|
50
|
+
`{{rulesDir}}/<правило>/SKILL.md`. Говорит, каким приёмом закон исполняется; на один закон их
|
|
51
|
+
бывает несколько.
|
|
52
|
+
|
|
53
|
+
```markdown
|
|
54
|
+
---
|
|
55
|
+
name: git-workflow
|
|
56
|
+
kind: rule
|
|
57
|
+
law: delivery
|
|
58
|
+
description: Правило под закон «Поставка». Брать на … Готовый код — в паттернах … Чем это названо здесь — в implementation.md рядом.
|
|
59
|
+
---
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Разделы: `## Когда берётся` · `## Что здесь действует` · `## Паттерны` · `## Ловушки`.
|
|
63
|
+
|
|
64
|
+
Имён этого дерева в правиле нет — оно переносимо ровно поэтому. Как что называется здесь и где
|
|
65
|
+
лежит, пишется рядом, в `implementation.md`, и оттуда же идёт привязка статей к коду:
|
|
66
|
+
|
|
67
|
+
```markdown
|
|
68
|
+
| Статья | Где исполняется |
|
|
69
|
+
| --------------------------------------------------------------- | ----------------------------- |
|
|
70
|
+
| Образы выкатываются по хешу коммита, а не по метке «последний». | `<состав прода>:<переменная>` |
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Каждый пункт раздела `## Что здесь действует` начинается жирной статьёй, и у каждой статьи есть
|
|
74
|
+
строка в компаньоне. Утверждение, которому места в коде не нашлось, в этот раздел не ставится:
|
|
75
|
+
оно уходит прозой в «Ловушки» или статьёй в закон.
|
|
76
|
+
|
|
77
|
+
## Паттерн
|
|
78
|
+
|
|
79
|
+
`{{rulesDir}}/<правило>-<что>/SKILL.md`. Минимум один на правило.
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
---
|
|
83
|
+
name: git-workflow-commit
|
|
84
|
+
kind: pattern
|
|
85
|
+
rule: git-workflow
|
|
86
|
+
description: Паттерн правила git-workflow. Брать … Не брать для … — это паттерн …
|
|
87
|
+
---
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Разделы: `## Когда брать` · готовый код · `## Частые промахи`. Компаньона у паттерна нет:
|
|
91
|
+
сверять готовый код с ним самим нечем.
|
|
92
|
+
|
|
93
|
+
## Порядок
|
|
94
|
+
|
|
95
|
+
1. Статья пишется в закон — без путей и имён файлов.
|
|
96
|
+
2. Правило объявляет закон в шапке и называет приём, которым статья исполняется.
|
|
97
|
+
3. Имена этого дерева и привязка каждой статьи уходят в `implementation.md` рядом; якорь
|
|
98
|
+
проверяется открытием файла, а не памятью.
|
|
99
|
+
4. Готовый код уезжает в паттерн, а правило на него ссылается.
|
|
100
|
+
5. Проверка спеков — до пуша.
|
|
101
|
+
|
|
102
|
+
## Частые промахи
|
|
103
|
+
|
|
104
|
+
- Закон назвал файл проекта. Путям место в правиле, а точнее — в его компаньоне.
|
|
105
|
+
- Компаньон лежит не рядом с правилом, а рядом с законом: он привязывает закон к этому проекту,
|
|
106
|
+
и зелёная проверка это утвердит, потому что структура совпадёт с тем, чего проверка сама и
|
|
107
|
+
ждёт.
|
|
108
|
+
- Якорь ведёт в мёртвый символ: объявлен и больше нигде не встречается.
|
|
109
|
+
- Статью переформулировали, а строку в привязке не тронули: связь идёт по тексту, и проверка
|
|
110
|
+
перестанет её находить.
|
|
111
|
+
- В описании не сказано, когда паттерн **не** брать, — соседний паттерн того же правила
|
|
112
|
+
становится неотличимым.
|