@rt-tools/agent-kit 0.5.3 → 0.7.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.
Files changed (85) hide show
  1. package/README.md +15 -1
  2. package/assets/checks/check-file-size.mjs +127 -0
  3. package/assets/checks/check-push-gate.mjs +139 -0
  4. package/assets/checks/rt-kit-checks.config.mjs +21 -0
  5. package/assets/defaults/gate-map.sh +90 -34
  6. package/assets/defaults/project.sh +26 -3
  7. package/assets/hooks/skill-gate-layers.sh +156 -0
  8. package/assets/hooks/skill-gate.sh +11 -2
  9. package/assets/laws/code-structure.md +3 -0
  10. package/assets/laws/delivery.md +9 -0
  11. package/assets/laws/observability.md +46 -0
  12. package/assets/laws/project-documentation.md +4 -0
  13. package/assets/laws/reuse-first.md +2 -0
  14. package/assets/laws/verifiability.md +5 -0
  15. package/assets/patterns/browser-verification-stand.md +22 -2
  16. package/assets/patterns/doc-style-trace.md +111 -0
  17. package/assets/patterns/git-workflow-commit.azure.md +18 -0
  18. package/assets/patterns/git-workflow-commit.github.md +19 -1
  19. package/assets/patterns/git-workflow-commit.gitlab.md +18 -0
  20. package/assets/patterns/git-workflow-docker.md +203 -0
  21. package/assets/patterns/git-workflow-secrets.md +93 -0
  22. package/assets/patterns/observability-record.md +114 -0
  23. package/assets/patterns/seo-verify.md +1 -1
  24. package/assets/patterns/spec-driven-domain.md +2 -2
  25. package/assets/patterns/spec-driven-rule.md +5 -0
  26. package/assets/patterns/styling-bem-sheet.md +178 -0
  27. package/assets/patterns/task-flow-close.md +20 -0
  28. package/assets/patterns/task-flow-resume.md +5 -0
  29. package/assets/patterns/translations-content.md +107 -0
  30. package/assets/patterns/translations-key.md +1 -1
  31. package/assets/rules/angular-patterns.md +5 -0
  32. package/assets/rules/browser-verification.md +17 -12
  33. package/assets/rules/component-structure.md +6 -2
  34. package/assets/rules/doc-style.md +16 -0
  35. package/assets/rules/git-workflow.azure.md +60 -1
  36. package/assets/rules/git-workflow.github.md +67 -1
  37. package/assets/rules/git-workflow.gitlab.md +61 -1
  38. package/assets/rules/lists.md +13 -0
  39. package/assets/rules/observability.md +147 -0
  40. package/assets/rules/permissions.md +23 -0
  41. package/assets/rules/reuse-first.md +9 -0
  42. package/assets/rules/seo.md +57 -9
  43. package/assets/rules/shared-code.md +6 -0
  44. package/assets/rules/spec-driven.md +9 -0
  45. package/assets/rules/styling-bem.md +34 -1
  46. package/assets/rules/task-flow.md +5 -0
  47. package/assets/rules/testing.md +46 -8
  48. package/assets/rules/translations.md +11 -5
  49. package/assets/rules/typescript-conventions.md +5 -0
  50. package/lib/assets.d.ts +1 -1
  51. package/lib/assets.d.ts.map +1 -1
  52. package/lib/assets.js +2 -4
  53. package/lib/assets.js.map +1 -1
  54. package/lib/catalog.d.ts +73 -0
  55. package/lib/catalog.d.ts.map +1 -1
  56. package/lib/catalog.js +121 -0
  57. package/lib/catalog.js.map +1 -1
  58. package/lib/commands.d.ts.map +1 -1
  59. package/lib/commands.js +84 -5
  60. package/lib/commands.js.map +1 -1
  61. package/lib/integrity.d.ts +25 -12
  62. package/lib/integrity.d.ts.map +1 -1
  63. package/lib/integrity.js +40 -18
  64. package/lib/integrity.js.map +1 -1
  65. package/lib/proposals.d.ts +9 -1
  66. package/lib/proposals.d.ts.map +1 -1
  67. package/lib/proposals.js +11 -2
  68. package/lib/proposals.js.map +1 -1
  69. package/lib/retired.d.ts +30 -0
  70. package/lib/retired.d.ts.map +1 -0
  71. package/lib/retired.js +19 -0
  72. package/lib/retired.js.map +1 -0
  73. package/lib/sync.d.ts +45 -1
  74. package/lib/sync.d.ts.map +1 -1
  75. package/lib/sync.js +44 -10
  76. package/lib/sync.js.map +1 -1
  77. package/package.json +1 -1
  78. package/rt-tools-agent-kit-0.7.0.tgz +0 -0
  79. package/assets/laws/application/money.md +0 -41
  80. package/assets/laws/application/ownership.md +0 -32
  81. package/assets/patterns/ownership-scope-resolve.md +0 -69
  82. package/assets/patterns/pricing-quote.md +0 -71
  83. package/assets/rules/ownership-scope.md +0 -63
  84. package/assets/rules/pricing.md +0 -64
  85. package/rt-tools-agent-kit-0.5.3.tgz +0 -0
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env bash
2
+ # Слои гейта правил: требования, которые приходят ПОВЕРХ доменного.
3
+ #
4
+ # Доменное правило выбирается один раз по пути файла — у правки один предмет, и правило под него
5
+ # одно. Слоёв поверх него полтора десятка: доступ к среде исполнения виден только в тексте
6
+ # правки, наблюдаемость приходит вместе с доменом, а не вместо него, проза и ведение работы
7
+ # судят тот же файл вторым признаком. Вместе они не помещаются в карту гейта, которую читают
8
+ # целиком, — поэтому лежат здесь.
9
+ #
10
+ # Подключается из `skill-gate.sh` в его же оболочке: читает `$input`, `$target` и `$req` и
11
+ # дописывает имена правил в `$req`. Отдельным процессом слои возвращали бы то же самое через
12
+ # диск.
13
+ #
14
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нечем разобрать вход — слой молчит. Разбор входа здесь побочная
15
+ # работа, и её поломка не имеет права остановить правку.
16
+ #
17
+ # Адреса дерева слои не знают: где у него бэкенд, витрина и сквозные тесты, говорит само дерево
18
+ # — функцией `skill_layer_skip <правило> <цель>` в своей карте гейта. Нет её — слой действует
19
+ # везде, где подошёл его признак.
20
+
21
+ # Уже названное вторым разом не требуется: отказ, перечисляющий одно правило дважды, читается
22
+ # как два разных требования.
23
+ rt_layer_add() {
24
+ case " $req " in
25
+ *" $1 "*) return 0 ;;
26
+ esac
27
+ req="${req:+$req }$1"
28
+ }
29
+
30
+ # Дерево вправе снять слой с места, где признак законен: прямое обращение к среде исполнения на
31
+ # бэкенде, чтение окружения в обвязке, заведение файла в сквозных тестах.
32
+ rt_layer_allowed() {
33
+ command -v skill_layer_skip >/dev/null 2>&1 || return 0
34
+ skill_layer_skip "$1" "$2" && return 1
35
+ return 0
36
+ }
37
+
38
+ # Текст правки читается один раз на все слои: разбор входа стоит дороже самих признаков.
39
+ rt_layer_payload=""
40
+ if command -v jq >/dev/null 2>&1; then
41
+ rt_layer_payload="$(printf '%s' "$input" \
42
+ | jq -r '[.tool_input.content, .tool_input.text, .tool_input.new_string, (.tool_input.edits[]?.new_string)]
43
+ | map(select(. != null)) | join("\n")' 2>/dev/null)"
44
+ fi
45
+
46
+ # Спека проверяет поведение, а не заводит его: там нужен `testing`, и слои поведения её обходят.
47
+ rt_layer_is_spec=1
48
+ case "$target" in *.spec.ts) rt_layer_is_spec=0 ;; esac
49
+
50
+ # --- слой по тексту: обращение к среде исполнения ---------------------------------------------
51
+ #
52
+ # Путь говорит, ЧТО за файл, а обращение к глобальному объекту видно только в содержимом: гейт,
53
+ # знающий один путь, пропускает его молча. Значение, полученное внедрением, признаком не
54
+ # считается — это уже зависимость, а не прямое обращение.
55
+ if [ -n "$rt_layer_payload" ] && [ "$rt_layer_is_spec" = 1 ]; then
56
+ case "$target" in
57
+ */main.ts|*/main.server.ts|*/server.ts|*/index.html) ;;
58
+ *.ts|*.html)
59
+ if printf '%s' "$rt_layer_payload" | grep -qE \
60
+ 'globalThis|PLATFORM_ID|isPlatformBrowser|defaultView|(^|[^[:alnum:]_.#$])window[[:space:]]*\.'; then
61
+ rt_layer_allowed platform-access "$target" && rt_layer_add platform-access
62
+ fi
63
+ ;;
64
+ esac
65
+ fi
66
+
67
+ # --- слой по тексту: наблюдаемость ------------------------------------------------------------
68
+ #
69
+ # Чтение переменной окружения и есть тот момент, когда заводится новая необязательная
70
+ # возможность. Сводка старта перечисляет их руками, и забывшая дописать себя не попадёт ни в
71
+ # один из трёх списков — на проде она выглядит не выключенной, а несуществующей.
72
+ if [ -n "$rt_layer_payload" ] && [ "$rt_layer_is_spec" = 1 ]; then
73
+ case "$target" in
74
+ *.ts)
75
+ if printf '%s' "$rt_layer_payload" | grep -qE 'process\.env'; then
76
+ rt_layer_allowed observability "$target" && rt_layer_add observability
77
+ fi
78
+ ;;
79
+ esac
80
+ fi
81
+
82
+ # --- слой по тексту: общий код ----------------------------------------------------------------
83
+ #
84
+ # Заводимое число-настройка и заводимое перечисление — тот момент, когда рядом с уже общим
85
+ # появляется копия. Каждая копия сама по себе исправна, и ни линт, ни сборка второй не видят.
86
+ # Спеки не в счёт: там значения местные, это фикстуры.
87
+ if [ -n "$rt_layer_payload" ] && [ "$rt_layer_is_spec" = 1 ]; then
88
+ case "$target" in
89
+ *.ts)
90
+ if printf '%s' "$rt_layer_payload" | grep -qE \
91
+ '(^|[[:space:]])(export[[:space:]]+)?const[[:space:]]+[A-Z][A-Z0-9_]*[[:space:]]*(:[[:space:]]*number[[:space:]]*)?=[[:space:]]*[0-9]|(^|[[:space:]])export[[:space:]]+enum[[:space:]]'; then
92
+ rt_layer_allowed shared-code "$target" && rt_layer_add shared-code
93
+ fi
94
+ ;;
95
+ esac
96
+ fi
97
+
98
+ # --- слой по пути: классы Angular -------------------------------------------------------------
99
+ #
100
+ # Компонент и стор — тоже классы: сигнальный API входов, обнаружение изменений и место подписки
101
+ # живут в `angular-patterns`, а первым слоем эти файлы уходят в устройство компонента и в
102
+ # соглашения языка, где ничего этого нет.
103
+ if [ "$rt_layer_is_spec" = 1 ]; then
104
+ case "$target" in
105
+ *.component.ts|*.store.ts)
106
+ rt_layer_allowed angular-patterns "$target" && rt_layer_add angular-patterns
107
+ ;;
108
+ esac
109
+ fi
110
+
111
+ # --- слой по пути: проза ----------------------------------------------------------------------
112
+ #
113
+ # Формат спека держит `spec-driven`, а как формулировать — `doc-style`, и нужен он не только
114
+ # спекам. Список известного у проверки — та же проза: его поле объясняет, что перечисленное
115
+ # отказом не считается, а сверка текстов читает только `.md`.
116
+ #
117
+ # Хозяйство самого агента слой обходит: правило на него — оно само, и карта гейта решает про
118
+ # эти файлы целиком. Слой, наложенный поверх, требовал бы правило там, где карта его нарочно
119
+ # не назвала.
120
+ case "$target" in
121
+ */.claude/*) ;;
122
+ *.md|*-allowlist.json) rt_layer_allowed doc-style "$target" && rt_layer_add doc-style ;;
123
+ esac
124
+
125
+ # --- слой по пути: ведение работы -------------------------------------------------------------
126
+ #
127
+ # Папка задачи, договорённость о продукте до кода и линия работ — первые файлы, которые
128
+ # заводятся в работе. Требование ловит на них того, кто пошёл мимо порядка: «пришла новая
129
+ # задача» инструментом не является, и поймать это больше нечем.
130
+ case "$target" in
131
+ */docs/tasks/*|*/docs/specs/*/proposed/*|*/docs/plans/*)
132
+ rt_layer_allowed task-flow "$target" && rt_layer_add task-flow
133
+ ;;
134
+ esac
135
+
136
+ # --- слой по заведению файла ------------------------------------------------------------------
137
+ #
138
+ # Ничего не пишется с нуля, и спрашивается это там, где решение и принимается, — на заведении
139
+ # нового файла: у правки существующего опора уже выбрана, а требовать правило на каждую строку
140
+ # значит сделать его фоном.
141
+ case "$target" in
142
+ */docs/*) ;;
143
+ *.spec.ts|*.stories.ts) ;;
144
+ *.ts|*.html|*.scss)
145
+ [ -f "$target" ] || { rt_layer_allowed reuse-first "$target" && rt_layer_add reuse-first; }
146
+ ;;
147
+ esac
148
+
149
+ # Куда встаёт заведённая проверка и какой формы у неё список известного — утверждения `testing`,
150
+ # и нужны они ровно в момент заведения: у существующей проверки и место в гейте, и форма списка
151
+ # уже выбраны.
152
+ case "$target" in
153
+ */check-*.mjs|*-allowlist.json)
154
+ [ -f "$target" ] || { rt_layer_allowed testing "$target" && rt_layer_add testing; }
155
+ ;;
156
+ esac
@@ -62,6 +62,11 @@ case "$tool" in
62
62
  # приходит в обычный сервис, а число-настройка в обычный класс.
63
63
  written="$(printf '%s' "$input" | jq -r '[.tool_input.content, .tool_input.text, .tool_input.new_string, (.tool_input.edits[]?.new_string)] | map(select(. != null)) | join("\n")' 2>/dev/null)"
64
64
  req="$(skill_for edit "$target" "$written" 2>/dev/null)"
65
+ # Слои поверх доменного правила лежат отдельным файлом и зовутся в этой же оболочке:
66
+ # доменное правило выбирается один раз по пути, а слоёв полтора десятка, и вместе они не
67
+ # помещаются в карту, которую читают целиком. Нет файла — гейт остаётся одним слоем.
68
+ # shellcheck disable=SC1090
69
+ [ -f "$rt_hooks_dir/skill-gate-layers.sh" ] && . "$rt_hooks_dir/skill-gate-layers.sh" 2>/dev/null
65
70
  # Род правки для наблюдения. Одно расширение, без пути и без имени файла: наблюдение
66
71
  # уезжает наружу, и всё, кроме рода, там было бы адресом этого дерева.
67
72
  case "${target##*/}" in
@@ -123,7 +128,11 @@ req="$want"
123
128
  [ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
124
129
  command -v rt_note >/dev/null 2>&1 && rt_note gate-deny "res=$req" "kind=$kind" "sid=$sid"
125
130
 
126
- reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill и повтори действие. Для этой области это происходит один раз за сессию."
131
+ # Запасной ход называется прямо в отказе: правило, заведённое в этой же ветке, реестру правил
132
+ # неизвестно — он собирается на запуске сессии, а гейт читает диск. Без этой строки следующий
133
+ # заход ищет обход перебором и обычно находит не тот.
134
+ fallback="Если инструмент такого имени не знает, правило завели после начала сессии — прочитай ${rules_dir}/${req}/SKILL.md и спутник рядом с ним."
135
+ reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill и повтори действие. ${fallback} Для этой области это происходит один раз за сессию."
127
136
 
128
137
  # Правило называет свой закон одним словом, а слоёв законов два: общий лежит в корне, закон
129
138
  # приложения — в каталоге под ним. Путь ищется, а не собирается из имени, иначе отказ ведёт в
@@ -134,7 +143,7 @@ if [ -n "$law" ]; then
134
143
  law_path="$laws_dir/${law}.md"
135
144
  [ -f "$root/$law_path" ] || law_path="$laws_dir/application/${law}.md"
136
145
  [ -f "$root/$law_path" ] \
137
- && reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill — оно применяет закон ${law_path} к этому дереву — и повтори действие. Для этой области это происходит один раз за сессию."
146
+ && reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill — оно применяет закон ${law_path} к этому дереву — и повтори действие. ${fallback} Для этой области это происходит один раз за сессию."
138
147
  fi
139
148
 
140
149
  jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
@@ -20,6 +20,9 @@
20
20
  причина названа рядом.
21
21
  - **Отметка об устаревании — повод убрать, а не повод оставить.** Устаревшее объявление,
22
22
  которое молча продолжает работать, переживает того, кто его пометил.
23
+ - **Файл читается целиком.** Длина, при которой его читают по частям, объявлена одним числом на
24
+ все роды файлов, и накопленное до объявления перечислено поимённо: перечень отмечает долг, а
25
+ не выдаёт разрешение.
23
26
 
24
27
  ## Открытые вопросы
25
28
 
@@ -75,3 +75,12 @@
75
75
  его написали, а разбора ждёт днями: за это время главная ветка вливается в ветку, и
76
76
  утверждение отчёта о соседних файлах становится неправдой молча — тел отчётов не читает ни
77
77
  одна проверка. Всё, что вливается в ветку после публикации отчёта, — повод перечитать его.
78
+ - **Слияние в главную ветку ещё не означает, что правка доехала.** Отказ выкатки не трогает ни
79
+ задачу, ни очередь работ, поэтому расхождение главной ветки с тем, что работает, обязано быть
80
+ видно там, где очередь читают. Иначе следующие работы вливаются поверх поломки, которую не
81
+ приносили, и каждая выглядит доехавшей.
82
+ - **Состоявшаяся поломка разбирается записью, которая переживает задачу.** Починка уезжает
83
+ веткой, задача закрывается — и причина, по которой приложение встало, остаётся знанием одного
84
+ исполнителя. Запись называет, что сломалось, чем это стало видно и почему починка чинит
85
+ причину, а не признак; живёт она среди описаний состоявшегося, а не там, что умирает вместе с
86
+ задачей.
@@ -0,0 +1,46 @@
1
+ # Закон о наблюдаемости
2
+
3
+ Что владелец знает о работе своего приложения. Если о поломке можно узнать только через доступ
4
+ к серверу, владелец узнаёт о ней от гостя и с опозданием.
5
+
6
+ **Ревизия:** 2026-08-13
7
+
8
+ ## Статьи
9
+
10
+ - **Отказ приложения виден владельцу без доступа к серверу.** На сервер владелец не заходит,
11
+ поэтому всё, что видно только оттуда, для него недоступно.
12
+ - **Отказ хранится и после перезапуска приложения, и спустя месяцы.** Поломку разбирают уже
13
+ после того, как она случилась, а вывод приложения к этому времени затирается.
14
+ - **Все отказы одного обращения находятся по одному признаку.** При десятке обращений в секунду
15
+ по времени их не разделить.
16
+ - **Тот, кто наткнулся на отказ, получает номер обращения и может его назвать.** Иначе по жалобе
17
+ нечего искать.
18
+ - **Наружу уходит номер обращения и общий текст ошибки. Подробности остаются внутри.**
19
+ Подробности показывают, как приложение устроено.
20
+ - **Подробности отказа закрыты правом.** В них видно, кто был в системе и чем именно приложение
21
+ сломалось.
22
+ - **Владелец узнаёт о новом отказе сам, без захода на экран.** Иначе поломку заметят только
23
+ тогда, когда до экрана дойдут руки.
24
+ - **У обращения к чужой службе есть предел ожидания.** Молчащая служба отличается от отказавшей
25
+ только временем: без предела обращение висит до умолчания среды, отказ не наступает вовсе, и
26
+ записывать нечего. Всё это время владелец видит исправную работу.
27
+ - **Обращение к чужой службе оставляет запись, и заводится она до обращения.** Запись после
28
+ ответа говорит только о том, что дошло: попытка, оборвавшаяся посередине, не отличается от не
29
+ начатой, а владелец видит лишь удачные исходы.
30
+ - **Отказ не записывается в ущерб работе приложения.** Ответ не ждёт записи, запрос из-за неё не
31
+ падает. Если записать не удалось, это видно.
32
+ - **Приложение не пишет отказы о собственной записи отказов.** Иначе поломка хранилища порождает
33
+ поток, который сам себя разгоняет.
34
+ - **Отказом считается то, что не сработало, а не каждый обслуженный запрос.** Иначе поток чужих
35
+ обращений определяет, сколько приложение о себе хранит.
36
+ - **У хранилища отказов есть предел, и он проверяется до того, как кончится место.** Иначе
37
+ хранилище отказов положит приложение, за которым следит.
38
+ - **Секреты и личные данные вычищаются перед записью.** После записи их убирают правкой данных,
39
+ а это дороже и не всегда возможно.
40
+
41
+ ## Открытые вопросы
42
+
43
+ - **Q-O-1.** Куда девать отказ, случившийся до того, как приложение поднялось. Хранилища в этот
44
+ момент ещё нет. Сейчас такой отказ остаётся только в выводе приложения.
45
+ - **Q-O-2.** Хранить ли отказы обвязки — того, что стоит перед приложением и отдаёт страницы.
46
+ Её вывод к приложению не приходит, и читать его надо отдельно.
@@ -33,6 +33,10 @@
33
33
  — это намерение, а не свойство приложения: сверить его не с чем, и оно проходит любую
34
34
  проверку. Машине это не поручить: открытый вопрос пишется теми же словами, что и обещание,
35
35
  и проверка отбивала бы оба.
36
+ - **Полнота текстов проверяется и со стороны работы, а не только со стороны текста.** Обход
37
+ написанного судит каждое утверждение, но утверждения, которого нет, в этом обходе нет тоже:
38
+ приём, применённый и нигде не описанный, так не находится никогда. Поэтому закрытая работа
39
+ спрашивается отдельно — оставила она след в текстах или явно его не требует.
36
40
  - **Документ утверждает о состоявшемся, а не о том, что должно сработать.** Лечение,
37
41
  записанное готовым до того, как его прогнали, дороже отсутствия записи: следующий читатель
38
42
  берёт его за проверенное — и берёт в тот день, когда лечение понадобилось, а времени на
@@ -8,6 +8,8 @@
8
8
 
9
9
  ## Статьи
10
10
 
11
+ - **У каждого приложения один источник вида, и он у них разный.** Взять контрол из чужого
12
+ источника значит принести на экран форму, которой в этом приложении нет больше нигде.
11
13
  - **Готовое берут, а не пишут заново.** Своя копия расходится с оригиналом с первой же правки,
12
14
  и одинаковые с виду места начинают вести себя по-разному.
13
15
  - **Отойти от общего вида может решить только владелец.** Сделать своё вместо готового
@@ -56,3 +56,8 @@
56
56
  род события и версия одинаковы везде, где стоит слой правил; путь, домен и имя дерева
57
57
  принадлежат одному дереву и в чужом месте не значат ничего, кроме утечки. Держится это
58
58
  проверкой на выносящей стороне, а не памятью того, кто пишет.
59
+ - **Проверка, которая сама сломалась, работу не останавливает.** Отказ инструмента не является
60
+ найденным нарушением, и остановленная им работа стоит до того, как его починят.
61
+ - **Решение, зависящее от текущего момента, получает момент снаружи.** Иначе проверить его можно
62
+ только подкруткой часов, а подкрученные часы действуют и на всё, что оказалось рядом: проверка
63
+ начинает зависеть от того, что к ней отношения не имеет.
@@ -50,12 +50,12 @@ npx nx build admin --base-href=/
50
50
  Angular DevTools, нужна ещё и dev-конфигурация (`--configuration=development`): прод-сборка не
51
51
  публикует `window.ng`. Выводы о размере бандла и минификации на такой сборке делать нельзя.
52
52
 
53
- Сессия кладётся в `localStorage['vm.admin.token']` **строкой JSON** (`JSON.stringify(token)`),
53
+ Сессия кладётся в `localStorage['<ключ сессии админки>']` **строкой JSON** (`JSON.stringify(token)`),
54
54
  иначе приложение её не прочитает. Токен не подписывается руками, а берётся у живого API:
55
55
  `POST /<область>.v1.AuthService/Login`. Команду с паролем классификатор блокирует — обходить не
56
56
  надо, спрашивать разрешение у владельца.
57
57
 
58
- Взять уже открытую сессию нельзя: чтение `localStorage['vm.admin.token']` из браузера
58
+ Взять уже открытую сессию нельзя: чтение того же ключа из браузера
59
59
  блокируется. Оба пути к своему стенду упираются в пароль, поэтому остаётся третий — смотреть
60
60
  на админке владельца, где вход уже сделан. Свой стенд нужен, только когда проверяют
61
61
  прод-сборку, `--base-href` или конфиг nginx; чтобы просто посмотреть экраны, он не нужен.
@@ -131,8 +131,28 @@ pnpm install --frozen-lockfile # из ../<префикс>-base: node_mod
131
131
  - Оба стенда держат поднятыми одновременно: если сравнивать по памяти между двумя запусками,
132
132
  заметишь только то, что успел запомнить.
133
133
 
134
+ ## Состояние портов снимается до первой сборки захода
135
+
136
+ Серверы владельца ложатся и без твоего участия: сайт отдавал 404 на все свои адреса ещё до
137
+ первой сборки захода, а API замолчало посреди него. Без замера «до» падение нельзя ни
138
+ приписать своей сборке, ни снять с неё подозрение.
139
+
140
+ ```bash
141
+ for u in http://localhost:{{apiPort}}/health http://localhost:{{sitePort}}/ http://localhost:{{adminPort}}/; do
142
+ printf '%s %s\n' "$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$u")" "$u"
143
+ done
144
+ ```
145
+
146
+ Замер — это ответ, а не `lsof`: порт бывает занят собранным артефактом прошлой сессии, и он
147
+ отвечает 200 старым кодом.
148
+
134
149
  ## Частые промахи
135
150
 
151
+ - **Одиночная сборка проекта серверы переживают, и отказываться от неё незачем.** Замерено
152
+ ответом до и после: все порты остались за своими процессами. Осторожность здесь стоит дороже
153
+ проверки — целая команда паттерна `seo-verify` осталась незапущенной ровно потому, что сборку
154
+ сочли опасной, не замерив. Сборка из кэша замером не является: она не собирает вовсе, и видно
155
+ это по её длительности.
136
156
  - Свой дев-сервер не поднимать: сайт на {{sitePort}}, админка на {{adminPort}}, API на {{apiPort}} уже подняты
137
157
  владельцем, и второй экземпляр отбивается гардом.
138
158
  - **Общая сборка глушит все три дев-сервера владельца, а не только API.** После
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: doc-style-trace
3
+ kind: pattern
4
+ rule: doc-style
5
+ description: Паттерн правила doc-style. Брать, когда полнота текстов проверяется со стороны работы, а не со стороны текста — обратный проход по закрытым задачам: признак отбора машиной, чего он не видит, три исхода по каждой задаче, граница «код не правится». Не брать для разбора документа, накопившего список работ, — это паттерн doc-style-sweep.
6
+ ---
7
+
8
+ # Обратный проход: закрытые задачи против текстов
9
+
10
+ Паттерн правила `doc-style`. Что при этом должно быть верно — закон
11
+ `docs/constitution/project-documentation.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Проверяется не то, верен ли текст, а то, есть ли он вообще.
16
+ - Разбирается работа, закрытая до того, как в закрытие задачи вошёл шаг приведения текстов.
17
+ - Волна разбора со стороны текста кончилась, а находок в ней вышло мало.
18
+
19
+ ## Проход со стороны текста не находит того, чего не написали
20
+
21
+ Он читает написанное и судит каждое утверждение; утверждения, которого нет, в обходе нет
22
+ тоже. Со стороны работы находок на порядок больше: в первой части семь находок из восьми
23
+ легли туда, где текста не было вовсе, а не туда, где он устарел.
24
+
25
+ ## Признак отбора — ветка внесла код и не тронула ни одного текста
26
+
27
+ Отбирается машиной и чтения не требует. Что ветка внесла на самом деле, отвечает история, а
28
+ не папка задачи: папка разбирается при закрытии, и таблица следа задачи до главной ветки не
29
+ доезжает вовсе.
30
+
31
+ ```bash
32
+ git log --merges --format='%H %s' <главная ветка> | grep -E 'from [^/]+/<ключ задач>-[0-9]+-'
33
+ git diff --name-only <мерж>^1 <мерж>
34
+ ```
35
+
36
+ Признак считается вычитанием, а не двумя списками путей: код — всё, что не `.md`, текст —
37
+ любой `.md` вне описания прошлого и папок задач. Списки, написанные руками, разошлись по обоим
38
+ краям сразу, и разошлись молча.
39
+
40
+ Список текстов пропустил самые читаемые тексты дерева: тот, что приходит в контекст каждой
41
+ сессии целиком, и тот, что описывает устройство дерева. Три задачи, тронувшие ровно их, попали
42
+ в выборку как бесследные — и во вторую часть прохода попали второй раз, уже как тронувшие
43
+ текст.
44
+
45
+ Список кода пропустил обвязку: гарды, конвейер и файлы выкатки в нём не значились, и две
46
+ задачи, правившие только их, не попали ни в одну выборку вовсе. Нашлись они не признаком, а
47
+ сверкой двух выборок между собой — её и стоит прогнать перед каждой следующей частью:
48
+
49
+ ```bash
50
+ comm -23 <список бесследных> <разобранные первой частью> # кого не прочитал никто
51
+ comm -12 <разобранные первой частью> <список тронувших текст> # кого прочитали дважды
52
+ ```
53
+
54
+ ## Часть назначает самая узкая область задачи, и метки бывает нет вовсе
55
+
56
+ Выборка, которую признак отобрал, одним заходом не читается, и делится она по области —
57
+ метке области у задачи. Меток у задачи бывает несколько; часть назначает самая узкая из них, а
58
+ порядок сужения выбирается один раз на весь проход и записывается в замысел вместе с таблицей
59
+ частей. При обратном порядке самые широкие области забрали бы себе все спорные задачи, и у
60
+ узких не осталось бы почти ничего.
61
+
62
+ Задача без единой метки области не попадает ни в одну часть вовсе — ни по какому порядку
63
+ сужения. Признак её отобрал, читать её некому, и видно это только пересчётом:
64
+
65
+ ```bash
66
+ awk -F'\t' 'NR==FNR{k[$1]=1;next} k[$1] && $2==""{print}' <номера выборки> <задачи с метками>
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
+ при живом тесте.
93
+
94
+ ## Находка кладётся в тот слой, которому принадлежит
95
+
96
+ - приём повторён и решается в коде — утверждение правила с привязкой в именах дерева;
97
+ - готовый код и порядок действий — паттерн;
98
+ - обещание, которое видит человек, — сценарий спека домена с прежней нумерацией;
99
+ - расхождение с договорённостью — текст статьи владельцу, файл закона не правится.
100
+
101
+ ## Частые промахи
102
+
103
+ - Состав части взят из прошлого захода, а не пересчитан признаком. Выборки живут в
104
+ скретчпаде сессии и умирают вместе с ней; в дерево они не кладутся — деление на части
105
+ свойство прохода, а не продукта. Пересчёт стоит двух команд выше и даёт тот же список, а
106
+ состав, принятый на слово, нечем сверить с соседними частями — именно сверкой находятся те,
107
+ кого не прочитал никто.
108
+ - Задача судится по своей папке: её разобрали при закрытии, и следа там не осталось.
109
+ - «След есть» по одному упоминанию: упоминание в соседнем правиле приёма не описывает.
110
+ - Находка записана только в паттерн: правило читают перед каждой правкой, паттерн — по имени.
111
+ - Признак пересчитан на новом списке путей без сверки на выборке руками.
@@ -201,6 +201,11 @@ az repos pr show --id 205 \
201
201
 
202
202
  Владельцу называют то, что прочитали, а не то, что заказывали.
203
203
 
204
+ Перечитывают его и по времени, а не только после вызовов, которые молча ничего не сделали:
205
+ состояние PR читается перед тем, как что-либо о нём сказать. Между «прогон зелёный» и следующей
206
+ фразой владелец успевает влить PR, и всё сказанное о нём после этого — про вчерашний день. Так
207
+ владельцу и было предложено влить то, что он влил часом раньше.
208
+
204
209
  Открытый PR означает, что элемент ждёт разбора, — состояние переставляется тем же движением:
205
210
 
206
211
  ```bash
@@ -236,6 +241,14 @@ npm run task:move -- 86 in-review
236
241
  сделано>`, тем же номером, что стоит у элемента и в имени ветки.
237
242
  12. **Очередь работ сходится** — `npm run check:board`.
238
243
  13. **Состояние PR прочитано, а не выведено из кодов возврата.**
244
+ 14. **Набор взят из файла конвейера, а не собран по памяти.** Гейт пуша заведомо уже: он стоит
245
+ между командой и пушем, и всё, что дольше секунд, из него вынесено. Что гоняет конвейер,
246
+ написано в его файле — этот список и повторяется локально; зелёный гейт полнотой набора не
247
+ является.
248
+ 15. **Набор пересмотрен после вливания главной ветки.** Он выбирается по тому, что ветка везёт
249
+ теперь, а не по тому, что правил автор. Ветка, не тронувшая ни строки показа, прогоняет
250
+ снимки витрин: с момента вливания их гоняет конвейер на её коде, и красное придёт на её
251
+ отчёт.
239
252
 
240
253
  Сразу после публикации элемент переводится в разбор, и сверка очереди прогоняется ещё раз: до
241
254
  открытия PR состояние она не судит, а после открытия расхождение видит.
@@ -243,6 +256,11 @@ npm run task:move -- 86 in-review
243
256
  Сделанное рассуждением и сделанное замером в теле PR разводятся прямо: непроверенное,
244
257
  названное проверенным, ревьювер принимает за проверенное.
245
258
 
259
+ **Раздел «Чем подтверждено» называет и то, что не гонялось.** Список одного прогнанного
260
+ неотличим от полного набора, и ревьювер по нему решает, что можно не перепроверять. Цена ошибки
261
+ здесь не красный конвейер, а доверие к разделу: однажды прочитанный как полнота, дальше он
262
+ перепроверяется весь.
263
+
246
264
  ## Частые промахи
247
265
 
248
266
  - Область и итерация не заданы: элемент заведён, но на доску команды не попал.
@@ -168,7 +168,7 @@ PR [<КЛЮЧ>-86] Письмо владельцу с незаполнен
168
168
  задача [<КЛЮЧ>-101] Вернуть оверлей загрузки таблицы и включить stylelint гейтом
169
169
  PR [<КЛЮЧ>-101] Stylelint включён гейтом
170
170
 
171
- задача [<КЛЮЧ>-212] Сайт не собирается: компонентам кита проставлен префикс vm- вместо rt-
171
+ задача [<КЛЮЧ>-212] Сайт не собирается: компонентам кита проставлен префикс приложения вместо своего
172
172
  PR [<КЛЮЧ>-212] Виджет переписки зовёт кит его собственными именами
173
173
  ```
174
174
 
@@ -249,6 +249,11 @@ $GH api "repos/$REPO/pulls/321" \
249
249
  Учётная запись, из-под которой пришлось пушить, в этот вызов не переносится: пуш и авторство
250
250
  PR выбираются отдельно, и `GH_TOKEN` для публикации — всегда токен бота.
251
251
 
252
+ Перечитывают его и по времени, а не только после вызовов, которые молча ничего не сделали:
253
+ состояние PR читается перед тем, как что-либо о нём сказать. Между «прогон зелёный» и следующей
254
+ фразой владелец успевает влить PR, и всё сказанное о нём после этого — про вчерашний день. Так
255
+ владельцу и было предложено влить то, что он влил часом раньше.
256
+
252
257
  Открытый PR означает, что задача ждёт разбора, — колонка переставляется тем же движением:
253
258
 
254
259
  ```bash
@@ -293,6 +298,14 @@ npm run task:move -- 86 in-review
293
298
  номером в заголовке; PR один на задачу, и закрывает он её целиком.
294
299
  13. **Состояние PR прочитано, а не выведено из кодов возврата** — автор `<бот>`,
295
300
  ревьювер — владелец, метки те же, что у задачи. Владельцу называют прочитанное.
301
+ 14. **Набор взят из файла конвейера, а не собран по памяти.** Гейт пуша заведомо уже: он стоит
302
+ между командой и пушем, и всё, что дольше секунд, из него вынесено. Что гоняет конвейер,
303
+ написано в его файле — этот список и повторяется локально; зелёный гейт полнотой набора не
304
+ является.
305
+ 15. **Набор пересмотрен после вливания главной ветки.** Он выбирается по тому, что ветка везёт
306
+ теперь, а не по тому, что правил автор. Ветка, не тронувшая ни строки показа, прогоняет
307
+ снимки витрин: с момента вливания их гоняет конвейер на её коде, и красное придёт на её
308
+ отчёт.
296
309
 
297
310
  Сразу после публикации задача переставляется в разбор — `npm run task:move -- <номер>
298
311
  in-review`, — и `npm run check:board` прогоняется ещё раз: до открытия PR колонку он не судит,
@@ -301,6 +314,11 @@ in-review`, — и `npm run check:board` прогоняется ещё раз:
301
314
  Сделанное рассуждением и сделанное замером в теле PR разводятся прямо: непроверенное,
302
315
  названное проверенным, ревьювер принимает за проверенное.
303
316
 
317
+ **Раздел «Чем подтверждено» называет и то, что не гонялось.** Список одного прогнанного
318
+ неотличим от полного набора, и ревьювер по нему решает, что можно не перепроверять. Цена ошибки
319
+ здесь не красный конвейер, а доверие к разделу: однажды прочитанный как полнота, дальше он
320
+ перепроверяется весь.
321
+
304
322
  ## Частые промахи
305
323
 
306
324
  - `gh` в оболочке пользователя подменён — звать `/opt/homebrew/bin/gh` напрямую.
@@ -221,6 +221,11 @@ glab mr view 205 --output json \
221
221
  Учётная запись, из-под которой пришлось пушить, в этот вызов не переносится: пуш и авторство
222
222
  MR выбираются отдельно, и токен для публикации — всегда токен машинной работы.
223
223
 
224
+ Перечитывают его и по времени, а не только после вызовов, которые молча ничего не сделали:
225
+ состояние MR читается перед тем, как что-либо о нём сказать. Между «прогон зелёный» и следующей
226
+ фразой владелец успевает влить MR, и всё сказанное о нём после этого — про вчерашний день. Так
227
+ владельцу и было предложено влить то, что он влил часом раньше.
228
+
224
229
  Открытый MR означает, что задача ждёт разбора, — список переставляется тем же движением:
225
230
 
226
231
  ```bash
@@ -256,6 +261,14 @@ npm run task:move -- 86 in-review
256
261
  сделано>`, тем же номером, что стоит у задачи и в имени ветки.
257
262
  12. **Очередь работ сходится** — `npm run check:board`.
258
263
  13. **Состояние MR прочитано, а не выведено из кодов возврата.**
264
+ 14. **Набор взят из файла конвейера, а не собран по памяти.** Гейт пуша заведомо уже: он стоит
265
+ между командой и пушем, и всё, что дольше секунд, из него вынесено. Что гоняет конвейер,
266
+ написано в его файле — этот список и повторяется локально; зелёный гейт полнотой набора не
267
+ является.
268
+ 15. **Набор пересмотрен после вливания главной ветки.** Он выбирается по тому, что ветка везёт
269
+ теперь, а не по тому, что правил автор. Ветка, не тронувшая ни строки показа, прогоняет
270
+ снимки витрин: с момента вливания их гоняет конвейер на её коде, и красное придёт на её
271
+ отчёт.
259
272
 
260
273
  Сразу после публикации задача переставляется в разбор, и сверка очереди прогоняется ещё раз: до
261
274
  открытия MR список она не судит, а после открытия расхождение видит.
@@ -263,6 +276,11 @@ npm run task:move -- 86 in-review
263
276
  Сделанное рассуждением и сделанное замером в теле MR разводятся прямо: непроверенное,
264
277
  названное проверенным, ревьювер принимает за проверенное.
265
278
 
279
+ **Раздел «Чем подтверждено» называет и то, что не гонялось.** Список одного прогнанного
280
+ неотличим от полного набора, и ревьювер по нему решает, что можно не перепроверять. Цена ошибки
281
+ здесь не красный конвейер, а доверие к разделу: однажды прочитанный как полнота, дальше он
282
+ перепроверяется весь.
283
+
266
284
  ## Частые промахи
267
285
 
268
286
  - Метка списка не поставлена при заведении: задача есть, а на доске её нет. Доска показывает