@rt-tools/agent-kit 0.8.3 → 0.9.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 (132) hide show
  1. package/README.md +12 -0
  2. package/assets/checks/board.github.mjs +48 -1
  3. package/assets/checks/check-board.github.mjs +84 -1
  4. package/assets/checks/check-lib-layers.mjs +13 -524
  5. package/assets/checks/check-specs.mjs +11 -782
  6. package/assets/checks/check-styles.mjs +185 -15
  7. package/assets/checks/lib-boundaries.mjs +143 -0
  8. package/assets/checks/lib-common.mjs +149 -0
  9. package/assets/checks/lib-domains.mjs +205 -0
  10. package/assets/checks/lib-manifests.mjs +60 -0
  11. package/assets/checks/lib-reexports.mjs +101 -0
  12. package/assets/checks/rt-kit-checks.config.mjs +17 -0
  13. package/assets/checks/spec-anchors.mjs +297 -0
  14. package/assets/checks/spec-common.mjs +222 -0
  15. package/assets/checks/spec-contract.mjs +152 -0
  16. package/assets/checks/spec-scenarios.mjs +201 -0
  17. package/assets/defaults/project.sh +8 -0
  18. package/assets/hooks/git-guard-push-tests.sh +8 -4
  19. package/assets/hooks/skill-gate.sh +1 -1
  20. package/assets/hooks/sql-guard-parse.sh +187 -0
  21. package/assets/hooks/sql-guard-request.sh +117 -0
  22. package/assets/hooks/sql-guard-target.sh +134 -0
  23. package/assets/hooks/sql-guard-write.sh +212 -0
  24. package/assets/hooks/sql-guard.sh +26 -596
  25. package/assets/hooks/waiting-turn-guard.sh +42 -13
  26. package/assets/laws/delivery.md +7 -0
  27. package/assets/laws/work-conduct.md +9 -0
  28. package/assets/patterns/admin-lists-screen.md +25 -14
  29. package/assets/patterns/admin-nav-item.md +1 -1
  30. package/assets/patterns/component-structure-new.md +1 -1
  31. package/assets/patterns/entity-aside.md +4 -2
  32. package/assets/patterns/observability-record.md +9 -0
  33. package/assets/patterns/shared-code-new.md +2 -2
  34. package/assets/patterns/task-flow-close.md +7 -1
  35. package/assets/rules/git-workflow.azure.md +7 -0
  36. package/assets/rules/git-workflow.github.md +41 -0
  37. package/assets/rules/git-workflow.gitlab.md +7 -0
  38. package/assets/rules/lib-layers.md +4 -0
  39. package/assets/rules/lists.md +10 -10
  40. package/assets/rules/shared-code.md +1 -1
  41. package/assets/rules/task-flow.md +47 -7
  42. package/assets/rules/testing.md +31 -0
  43. package/assets/rules/typescript-conventions.md +7 -0
  44. package/assets/skills/agent-kit.md +36 -0
  45. package/assets/templates/proposal.md +21 -0
  46. package/bin/agent-kit.d.ts.map +1 -1
  47. package/bin/agent-kit.js +115 -87
  48. package/bin/agent-kit.js.map +1 -1
  49. package/index.d.ts +1 -0
  50. package/index.d.ts.map +1 -1
  51. package/index.js +1 -0
  52. package/index.js.map +1 -1
  53. package/lib/argv.d.ts.map +1 -1
  54. package/lib/argv.js +6 -4
  55. package/lib/argv.js.map +1 -1
  56. package/lib/assets.d.ts.map +1 -1
  57. package/lib/assets.js +2 -1
  58. package/lib/assets.js.map +1 -1
  59. package/lib/cargo.d.ts +20 -0
  60. package/lib/cargo.d.ts.map +1 -1
  61. package/lib/cargo.js.map +1 -1
  62. package/lib/cascade.d.ts +55 -0
  63. package/lib/cascade.d.ts.map +1 -0
  64. package/lib/cascade.js +131 -0
  65. package/lib/cascade.js.map +1 -0
  66. package/lib/catalog.d.ts +0 -75
  67. package/lib/catalog.d.ts.map +1 -1
  68. package/lib/catalog.js +44 -127
  69. package/lib/catalog.js.map +1 -1
  70. package/lib/commands.d.ts.map +1 -1
  71. package/lib/commands.js +152 -85
  72. package/lib/commands.js.map +1 -1
  73. package/lib/companion.d.ts.map +1 -1
  74. package/lib/companion.js +5 -5
  75. package/lib/companion.js.map +1 -1
  76. package/lib/config.d.ts +2 -0
  77. package/lib/config.d.ts.map +1 -1
  78. package/lib/config.js +7 -5
  79. package/lib/config.js.map +1 -1
  80. package/lib/enroll.d.ts +56 -0
  81. package/lib/enroll.d.ts.map +1 -0
  82. package/lib/enroll.js +123 -0
  83. package/lib/enroll.js.map +1 -0
  84. package/lib/freshness.d.ts.map +1 -1
  85. package/lib/freshness.js +31 -17
  86. package/lib/freshness.js.map +1 -1
  87. package/lib/hooks-map.d.ts +30 -0
  88. package/lib/hooks-map.d.ts.map +1 -1
  89. package/lib/hooks-map.js +80 -18
  90. package/lib/hooks-map.js.map +1 -1
  91. package/lib/integrity.d.ts +1 -2
  92. package/lib/integrity.d.ts.map +1 -1
  93. package/lib/integrity.js +0 -1
  94. package/lib/integrity.js.map +1 -1
  95. package/lib/observations.d.ts.map +1 -1
  96. package/lib/observations.js +25 -12
  97. package/lib/observations.js.map +1 -1
  98. package/lib/order.d.ts +10 -0
  99. package/lib/order.d.ts.map +1 -0
  100. package/lib/order.js +14 -0
  101. package/lib/order.js.map +1 -0
  102. package/lib/picker.d.ts.map +1 -1
  103. package/lib/picker.js +8 -2
  104. package/lib/picker.js.map +1 -1
  105. package/lib/plan.js +1 -1
  106. package/lib/plan.js.map +1 -1
  107. package/lib/proposals.d.ts.map +1 -1
  108. package/lib/proposals.js +25 -8
  109. package/lib/proposals.js.map +1 -1
  110. package/lib/sections.js +1 -1
  111. package/lib/sections.js.map +1 -1
  112. package/lib/ship.d.ts.map +1 -1
  113. package/lib/ship.js +9 -1
  114. package/lib/ship.js.map +1 -1
  115. package/lib/shipment.d.ts.map +1 -1
  116. package/lib/shipment.js +14 -10
  117. package/lib/shipment.js.map +1 -1
  118. package/lib/snapshot.d.ts.map +1 -1
  119. package/lib/snapshot.js +2 -1
  120. package/lib/snapshot.js.map +1 -1
  121. package/lib/stamp.js +1 -1
  122. package/lib/stamp.js.map +1 -1
  123. package/lib/sync.d.ts +12 -2
  124. package/lib/sync.d.ts.map +1 -1
  125. package/lib/sync.js +11 -10
  126. package/lib/sync.js.map +1 -1
  127. package/lib/vars.d.ts.map +1 -1
  128. package/lib/vars.js +2 -3
  129. package/lib/vars.js.map +1 -1
  130. package/package.json +1 -1
  131. package/rt-tools-agent-kit-0.9.0.tgz +0 -0
  132. package/rt-tools-agent-kit-0.8.3.tgz +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: Stop
3
- # Гард ожидания: ход, в котором открыт PR, не заканчивается, пока в нём не было ни одного
4
- # действия по следующей задаче. Stop.
3
+ # Гард ожидания: ход, сообщающий владельцу о чужом шаге, не заканчивается, пока в нём не было ни
4
+ # одного действия по следующей задаче. Stop.
5
5
  #
6
6
  # Зачем именно так. Статья «ожидание прогона работой не занимают» держится памятью исполнителя, и
7
7
  # держится плохо: образец сообщения владельцу кончается фразой о следующей задаче, а фраза
@@ -9,17 +9,22 @@
9
9
  # ничем себя не выдаёт: ни правкой файла, ни командой, — и промах виден только владельцу, только
10
10
  # по тому, что работа не двигается, и только когда он спросит прямо.
11
11
  #
12
- # Признак берётся из хода, а не из сети. Спросить хостинг об открытых PR было бы точнее, но
13
- # сетевой вызов на завершении хода падает вместе со связью и отбивал бы работу вместо промаха.
14
- # Поэтому судится пара: в ходе был вызов открытия PR и в том же ходе было действие по
15
- # следующей задаче.
12
+ # Чужой шаг узнаётся по двум признакам, и оба берутся из хода, а не из сети. Первый открытие
13
+ # PR: работа отдана, и дальше её двигает владелец. Второй прочитанный красный прогон: чинить
14
+ # его надо, но ход, в котором его прочитали и не сделали ничего, та же пустота. Само чтение
15
+ # прогона признаком не является: конец прогона читают и затем, чтобы пойти чинить; значит
16
+ # красный ответ, а он лежит в выводе команды — там же, в записи хода.
17
+ #
18
+ # Спросить хостинг было бы точнее, но сетевой вызов на завершении хода падает вместе со связью и
19
+ # отбивал бы работу вместо промаха.
16
20
  #
17
21
  # Что считается действием: заведение задачи, заведение ветки, перевод колонки очереди работ,
18
22
  # заведение папки задачи. Набор открыт и пополняется правкой — полнота его открытый вопрос, а не
19
23
  # обещание.
20
24
  #
21
- # Чего гард не судит. Ход, в котором PR не открывали, — здесь он молчит: пустой ход неотличим
22
- # от хода, которому нечего было делать. Это известная его граница.
25
+ # Чего гард не судит. Ход, в котором о чужом шаге не сказано ничего, — здесь он молчит: пустой
26
+ # ход неотличим от хода, которому нечего было делать. Это известная его граница. Зелёный прогон
27
+ # он тоже не судит: за ним идёт своя работа — уборка и снятие черновика, — а не чужой шаг.
23
28
  #
24
29
  # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода и повторном
25
30
  # заходе ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
@@ -42,15 +47,23 @@ transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/nu
42
47
  # ничего.
43
48
  opened_re='gh[^|;&]*pr[[:space:]]+create|api[^|;&]*-X[[:space:]]+POST[^|;&]*/pulls|glab[^|;&]*mr[[:space:]]+create|az[[:space:]]+repos[[:space:]]+pr[[:space:]]+create'
44
49
 
50
+ # Чтение прогона — второй случай чужого шага. Сам по себе он ничего не значит: конец прогона
51
+ # читают и затем, чтобы пойти чинить. Значит красный ответ, а он лежит не в команде, а в её
52
+ # выводе — там же, в записи хода, и сети для этого не нужно.
53
+ read_re='run[[:space:]]+(list|view|watch)|pipelines[[:space:]]+runs'
54
+ red_re='completed[[:space:]]+failure|"conclusion"[[:space:]]*:[[:space:]]*"failure"|conclusion:[[:space:]]*failure|completed with .failure.|run[[:space:]]+failed'
55
+
45
56
  # Первое действие по следующей задаче. Заведение папки стоит здесь наравне с командами: работа
46
- # по уже заведённому номеру начинается именно с неё.
57
+ # по уже заведённому номеру начинается именно с неё. Правка кода в ветке того же PR действием
58
+ # по следующей задаче не считается — она чинит прежнюю, а не двигает работу дальше.
47
59
  moved_re='task:new|task:move|checkout[[:space:]]+-b|docs/tasks/'
48
60
 
49
61
  # Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
50
62
  # приходит той же ролью, поэтому строки с `tool_result` вводом не считаются.
51
63
  #
52
64
  # Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
53
- verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg opened "$opened_re" --arg moved "$moved_re" '
65
+ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
66
+ --arg opened "$opened_re" --arg moved "$moved_re" --arg read "$read_re" --arg red "$red_re" '
54
67
  def is_input:
55
68
  .type == "user"
56
69
  and (((.message.content // []) | if type == "array"
@@ -61,14 +74,30 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg opened "$opene
61
74
  | (if $i == null then [] else .[$i:] end) as $turn
62
75
  | [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "tool_use")] as $uses
63
76
  | ($uses | map((.input.command // "")) | join("\n")) as $ran
77
+ # Ответ инструмента берётся текстом, а не в записи JSON: у той таб и кавычка уезжают
78
+ # экранированными, и образец по ним не попадает вовсе. Приходит он и строкой, и списком
79
+ # блоков — обе формы сводятся к одному тексту.
80
+ | ([$turn[] | select(.type == "user") | .message.content // [] | select(type == "array") | .[]
81
+ | select(.type == "tool_result") | .content
82
+ | if type == "string" then .
83
+ elif type == "array" then (map(if type == "object" then (.text // "") else tostring end) | join("\n"))
84
+ else tostring end] | join("\n")) as $out
64
85
  | ($ran | test($opened; "i")) as $opened_pr
86
+ | (($ran | test($read; "i")) and ($out | test($red; "i"))) as $red_run
65
87
  | ($ran | test($moved; "i")) as $went_on
66
- | if $opened_pr and ($went_on | not) then "owe" else "pass" end
88
+ | if $went_on then "pass"
89
+ elif $opened_pr then "owe:pr"
90
+ elif $red_run then "owe:run"
91
+ else "pass" end
67
92
  ' 2>/dev/null)"
68
93
 
69
- [ "$verdict" = "owe" ] || exit 0
94
+ case "$verdict" in
95
+ owe:pr) said="в этом ходе открыт PR" ;;
96
+ owe:run) said="в этом ходе прочитан красный прогон" ;;
97
+ *) exit 0 ;;
98
+ esac
70
99
 
71
- reason="BLOCKED by waiting-turn-guard: в этом ходе открыт PR, а действия по следующей задаче в нём нет ни одного. Ожидание чужого шага заходом не занимают: прогон идёт на стороне и быстрее от взгляда на него не становится.
100
+ reason="BLOCKED by waiting-turn-guard: ${said}, а действия по следующей задаче в нём нет ни одного. Ожидание чужого шага заходом не занимают: прогон, разбор и слияние идут на стороне и быстрее от взгляда не становятся.
72
101
 
73
102
  Сказать «беру следующую задачу» — не то же самое, что взять её: фраза живёт до конца хода, а работа не двигается, и заметить это может только владелец.
74
103
 
@@ -109,6 +109,13 @@
109
109
  человека и только про названный PR; молчание разрешением не бывает, а слово, сказанное об
110
110
  одном PR, на следующий не переносится. Иначе разбор проходит тот, кого разбирают, и
111
111
  очередь PR выглядит разобранной, не будучи ею.
112
+ - **Требование, стоящее перед необратимым шагом, стоит там, где этот шаг совершают.** Гард на
113
+ машине исполнителя судит его команды и молчит о том же действии, совершённом кнопкой у
114
+ хостинга: обход выходит не намеренным, а незамеченным — нажавший не знает, что чего-то не
115
+ хватало. Требование либо переносится туда, где нажимают, либо объявляется тому, кто нажимает,
116
+ до нажатия. Иначе оно держится не собой, а тем, что необратимый шаг каждый раз делает тот же
117
+ человек: папка закрытой задачи так и уехала в главную ветку вместе со своей договорённостью, и
118
+ вынимать её пришлось отдельной задачей.
112
119
  - **Слияние в главную ветку ещё не означает, что правка доехала.** Отказ выкатки не трогает ни
113
120
  задачу, ни очередь работ, поэтому расхождение главной ветки с тем, что работает, обязано быть
114
121
  видно там, где очередь читают. Иначе следующие работы вливаются поверх поломки, которую не
@@ -13,6 +13,15 @@
13
13
  - **Пробел закрывается вопросом владельцу, а не догадкой.** Догадка неотличима от знания:
14
14
  она попадает в работу молча и обнаруживается только при приёмке, когда переделывать дороже
15
15
  всего.
16
+ - **Работа, объявленная сборкой по образцу, начинается с чтения самого образца.** Пересказ
17
+ образца образцом не является: по нему собирается понимание того, кто пересказывал, и
18
+ расхождение всплывает на приёмке целой работой, а не строкой. Читается та часть образца,
19
+ которую работа повторяет, и читается целиком — об устройстве чужого дерева по одному его
20
+ файлу не судят.
21
+ - **Образец, названный однажды, доступен каждому заходу работы.** Где он лежит, записано
22
+ там, где заход найдёт это без владельца. Иначе второй заход собирает по памяти первого,
23
+ третий — по пересказу второго, и к четвёртому от образца не остаётся ничего, кроме слова
24
+ «образец».
16
25
  - **Вопрос, у которого есть очевидный ответ, работу не останавливает.** Исполнитель называет
17
26
  допущение, идёт дальше и записывает его туда же, где идёт работа. Останавливает только тот
18
27
  пробел, при котором любая догадка делает работу опасной или бесполезной. Вопрос, заданный
@@ -2,7 +2,7 @@
2
2
  name: admin-lists-screen
3
3
  kind: pattern
4
4
  rule: lists
5
- description: Паттерн правила lists. Брать при сборке или правке списочного экрана админки — готовый порядок блоков, разметка <префикс>-table, клик по строке, меню строки с vmTableRowActionsRowType, сортируемый заголовок, слоты тулбара, тост отказа.
5
+ description: Паттерн правила lists. Брать при сборке или правке списочного экрана админки — готовый порядок блоков, разметка <префикс>-table, клик по строке, меню строки с колонкой действий и предикатом строки, сортируемый заголовок, слоты тулбара, тост отказа.
6
6
  ---
7
7
 
8
8
  # Собрать списочный экран
@@ -25,7 +25,7 @@ description: Паттерн правила lists. Брать при сборке
25
25
  <header rtElem="header">
26
26
  <!-- div rtElem="header-main" c h1 rtElem="title" + p rtElem="hint" -->
27
27
  <<префикс>-toolbar>
28
- <!-- vmToolbarLeft / vmToolbarRight -->
28
+ <!-- <префикс>ToolbarLeft / <префикс>ToolbarRight -->
29
29
  <div rtElem="scroll">
30
30
  <!-- overflow-x: auto -->
31
31
  <<префикс>-table rtElem="table">
@@ -42,9 +42,9 @@ description: Паттерн правила lists. Брать при сборке
42
42
  ## Таблица
43
43
 
44
44
  ```html
45
- <<префикс>-table #rowsTable="vmTable" rtElem="table" clickable [ariaLabel]="'bookingsTableAria' | transloco" [emptyMessage]="'bookingsEmpty'
46
- | transloco" [tableId]="tableId" [dataSource]="rows()" [columnsConfig]="columnsConfig()" [rowHasActions]="hasRowActions"
47
- [loading]="loading()">
45
+ <<префикс>-table #rowsTable="<префикс>Table" rtElem="table" clickable [ariaLabel]="'recordsTableAria' | transloco"
46
+ [emptyMessage]="'recordsEmpty' | transloco" [tableId]="tableId" [dataSource]="rows()" [columnsConfig]="columnsConfig()"
47
+ [rowHasActions]="hasRowActions" [loading]="loading()">
48
48
  ```
49
49
 
50
50
  - `tableId` — ключ, под которым хранится выбор столбцов; он же уходит в асайд настроек.
@@ -57,8 +57,8 @@ description: Паттерн правила lists. Брать при сборке
57
57
  <tr
58
58
  *cdkRowDef="let row; columns: rowsTable.displayedColumns()"
59
59
  cdk-row
60
- qa-dataid="bookings-row"
61
- vmTableRow
60
+ qa-dataid="records-row"
61
+ <префикс>TableRow
62
62
  (activated)="openAside(row)"></tr>
63
63
  ```
64
64
 
@@ -67,13 +67,24 @@ description: Паттерн правила lists. Брать при сборке
67
67
  ## Меню строки
68
68
 
69
69
  ```html
70
- <ng-template vmTableRowActions let-row [vmTableRowActionsRowType]="rows()"></ng-template>
70
+ <<префикс>-table [showRowActions]="true" [rowHasActions]="hasRowActions" [loading]="loading()">
71
+ <ng-template <префикс>TableRowActions let-row [<префикс>TableRowActionsRowType]="rows()"></ng-template>
71
72
  ```
72
73
 
73
74
  ```typescript
74
- protected readonly hasRowActions = (row: IBooking.State): boolean => row.canConfirm || row.canReject;
75
+ protected readonly hasRowActions: I<Префикс>Table.RowActionsPredicate<IRecord.Row> = recordRowHasActions;
75
76
  ```
76
77
 
78
+ Входа два, и они не заменяют друг друга. `showRowActions` заводит саму колонку действий — по
79
+ умолчанию он опущен, и без него таблица рисует одни объявленные столбцы: ни шаблон действий, ни
80
+ предикат колонки не добавляют. `rowHasActions` решает, показывать ли кнопку у конкретной
81
+ строки, и колонки не заводит вовсе. Экран ролей так и вышел с четырьмя столбцами и
82
+ недостижимыми действиями: сборка, линт и юниты на правилах строки при этом зелёные, а нашёл
83
+ это прогон сквозной спеки.
84
+
85
+ Ячейка действий скрыта, пока указатель не на строке, — сквозная спека наводит на строку до
86
+ нажатия.
87
+
77
88
  Доступность действия лежит полем строки (`canConfirm`, `canReject`), а не вызовом метода
78
89
  компонента. Действие, которого записи нельзя сделать, из меню убирается целиком. Необратимое
79
90
  несёт `danger`, `confirmTitle` и `confirmMessage` с последствием — не «Вы уверены?», а что
@@ -82,7 +93,7 @@ protected readonly hasRowActions = (row: IBooking.State): boolean => row.canConf
82
93
  ## Сортируемый заголовок
83
94
 
84
95
  ```html
85
- <th *cdkHeaderCellDef cdk-header-cell vmSortHeader="checkIn">{{ 'bookingsCheckIn' | transloco }}</th>
96
+ <th *cdkHeaderCellDef cdk-header-cell <префикс>SortHeader="createdAt">{{ 'recordsCreatedAt' | transloco }}</th>
86
97
  ```
87
98
 
88
99
  Колонка помечается `sortable: true` в `columnsConfig`. Заголовок переключает сортировку по
@@ -95,8 +106,8 @@ protected readonly hasRowActions = (row: IBooking.State): boolean => row.canConf
95
106
 
96
107
  ## Тулбар
97
108
 
98
- Тулбар поделён на две части слотами: `vmToolbarLeft` — то, что меняет выборку,
99
- `vmToolbarRight` — действия над списком. Своей раскладки внутри тулбара экран не заводит.
109
+ Тулбар поделён на две части слотами: `<префикс>ToolbarLeft` — то, что меняет выборку,
110
+ `<префикс>ToolbarRight` — действия над списком. Своей раскладки внутри тулбара экран не заводит.
100
111
 
101
112
  Левый слот — фильтры и поиск. Правый — иконки `<префикс>-icon-button variant="primary"` с парой
102
113
  `tooltip` + `ariaLabel` одного текста: обновление (`sync`), настройки столбцов (`sliders-v`),
@@ -105,7 +116,7 @@ protected readonly hasRowActions = (row: IBooking.State): boolean => row.canConf
105
116
  ## Отказ загрузки
106
117
 
107
118
  ```typescript
108
- this.#notifications.error(this.#transloco.translate(this.#store.errorKey() ?? 'bookingsLoadFailed'));
119
+ this.#notifications.error(this.#transloco.translate(this.#store.errorKey() ?? 'recordsLoadFailed'));
109
120
  ```
110
121
 
111
122
  Ключ читается сразу после запроса, а не подпиской на сигнал стора: стор делят список и панель
@@ -118,7 +129,7 @@ this.#notifications.error(this.#transloco.translate(this.#store.errorKey() ?? 'b
118
129
  npx nx build admin
119
130
  ```
120
131
 
121
- Продовая сборка обязательна: без `[vmTableRowActionsRowType]` тип `let-row` выводится как
132
+ Продовая сборка обязательна: без `[<префикс>TableRowActionsRowType]` тип `let-row` выводится как
122
133
  `unknown`, и падает только она — юниты и дев-сервер проходят.
123
134
 
124
135
  ## Частые промахи
@@ -65,7 +65,7 @@ description: Паттерн правила navigation. Брать при зав
65
65
  - Обход одного верхнего ряда при поиске владельца адреса: разделы обходятся вместе с пунктами
66
66
  их панелей, иначе вложенный экран остаётся без гейтинга.
67
67
  - Подсказка у доступного пункта: она дословно повторяет подпись рядом и не добавляет ничего.
68
- - `vmPopoverTrigger` не поставлен явно: директива попапа по умолчанию открывается нажатием, и
68
+ - `<префикс>PopoverTrigger` не поставлен явно: директива попапа по умолчанию открывается нажатием, и
69
69
  два соседних элемента одного ряда ведут себя по-разному.
70
70
  - Переезд адреса без правки сквозных спек: они ходят по адресам, и прогон станет красным.
71
71
  - Иконка, подобранная «похожая»: отсутствующая добавляется в спрайт и в `<префикс>-icon-names.ts`.
@@ -77,7 +77,7 @@ export class ComponentNameComponent {
77
77
  ## `qa-dataid` — на каждый интерактивный элемент
78
78
 
79
79
  ```html
80
- <button vmButton qa-dataid="calendar-retry-prices" type="button" (click)="retryPrices.emit()">Повторить</button>
80
+ <button <префикс>Button qa-dataid="calendar-retry-prices" type="button" (click)="retryPrices.emit()">Повторить</button>
81
81
  <div rtElem="grid" qa-dataid="admin-calendar-grid"></div>
82
82
  ```
83
83
 
@@ -132,5 +132,7 @@ constructor() {
132
132
  обводку фокуса у края прокрутки.
133
133
  - Панель, остающаяся открытой после успеха, не сбросила нетронутость (`markAsPristine`) —
134
134
  вопрос о правках задаётся сразу после записи.
135
- - Отключённые поля ввода вместо данных: запись, которую только смотрят, показывается через
136
- `<dl>`, `<префикс>-detail-list`, `<префикс>-info-item`.
135
+ - Отключённые поля ввода вместо данных: запись, которую только смотрят, показывается готовым
136
+ списком свойств кита — `<префикс>-detail-list`, `<префикс>-info-item`. Своя разметка на `<dl>`
137
+ идёт последней и только как разовое отступление: она и есть отход от общего вида, а решает
138
+ его владелец, — перечень, ставящий её первой, учит обходить готовое раньше, чем спросить.
@@ -112,3 +112,12 @@ const response: Response = await fetch(RATES_URL, { signal: AbortSignal.timeout(
112
112
  приложение при этом работает.
113
113
  - **Ошибка положена в поле как есть, без разбора причины.** Объект ошибки сериализуется в
114
114
  пустой `{}`, и в логе не останется ни текста, ни класса.
115
+ - **Незаполненная часть ключа группы стоит пропуском, а не пустой строкой.** Два отказа без
116
+ процедуры и без предмета работы обязаны сойтись в одну группу; с пропуском в ключе каждый
117
+ заводит свою, и лента наполняется одиночными строками об одном и том же.
118
+ - **Одинаковые отказы, пришедшие разом, дают две группы.** Между «такой группы ещё нет» и
119
+ вставкой успевает пройти второй, поэтому случай разбирается не проверкой перед вставкой, а
120
+ после того, как вставка не прошла.
121
+ - **Имя строки лога длиннее предела ключа не записывается вовсе.** Оно собирается подстановкой
122
+ и ничем не ограничено, а у ключа предел есть: без обрезки отказ с длинным именем теряется
123
+ молча — ни строки в ленте, ни отказа в логах.
@@ -47,14 +47,14 @@ export const DEFAULT_PAGE_SIZE: number = 20;
47
47
  решает вызывающий.
48
48
 
49
49
  ```typescript
50
- const operator: FilterOperatorType | null = listFilterOperatorOf(filter.operatorType);
50
+ const operator: TFilterOperatorType | null = listFilterOperatorOf(filter.operatorType);
51
51
  if (!operator) {
52
52
  throw new ConnectError(`filter operator is required: ${filter.propertyName}`, Code.InvalidArgument);
53
53
  }
54
54
  ```
55
55
 
56
56
  ```typescript
57
- const direction: ListSortOrderType = listSortOrderOf(rawDirection) ?? LIST_SORT_ORDER_ENUM.ASC;
57
+ const direction: TListSortOrderType = listSortOrderOf(rawDirection) ?? EListSortOrder.ASC;
58
58
  ```
59
59
 
60
60
  Сервер отбивает запрос, экран берёт умолчание. Общий маппер здесь не годится: он подал бы
@@ -41,9 +41,15 @@ PR открыт черновиком — и с этой минуты работ
41
41
  ```
42
42
  PR #<номер> открыт черновиком. Жду прогона: пока он идёт, о работе известно только то, что
43
43
  она запушена. Как закончится — разберу папку задачи последним коммитом, сниму черновик и
44
- попрошу тебя влить. Пока жду, беру задачу #<номер следующей>.
44
+ попрошу тебя влить. Следующая задача уже взята: #<номер>, ветка <имя ветки>.
45
45
  ```
46
46
 
47
+ Последняя строка называет взятое, а не намерение взять, и это не оборот речи. Образец, который
48
+ кончается обещанием, исполняется как обещание: заход произносит последнюю строку и на этом
49
+ кончает ход — сообщение при этом выглядит полным, и пустоты за ним не видно ни владельцу, ни
50
+ самому заходу. Образец, который кончается номером заведённой ветки, так исполнить нельзя: пока
51
+ ветки нет, строку писать нечем. Разбор — `2026-08-16-next-task-said-not-taken.md`.
52
+
47
53
  Прогон зелёный, папка разобрана и запушена, черновик снят:
48
54
 
49
55
  ```
@@ -168,6 +168,13 @@ flowchart TD
168
168
  отбивает пуш, а не печатается рядом с ним: напечатанное предупреждение исполнитель читает как
169
169
  разрешение. Дважды подряд правка, прошедшая гейт целиком, была отбита конвейером — и оба раза
170
170
  зелёный гейт был прочитан как «локально всё зелено».
171
+ - **Сверка раскладки стоит в наборе гейта пуша наравне с линтом и сборкой.** Правка, положенная
172
+ в разложенную копию мимо источника, в день, когда её делают, не ломает ничего: дерево
173
+ работает, проверки зелёные, а расхождение видно только тому, кто позовёт сверку сам. Копится
174
+ оно молча и всплывает на чужой работе — раскладка отказывает по правленому файлу целиком и не
175
+ кладёт ни одного другого, так что цену платит тот, кто правил соседний ресурс. Статьёй выше
176
+ эта строка не покрывается: сверки нет в конвейере, а значит нет и шага, который она бы
177
+ закрывала, — в набор она ставится прямо, а не выводится из его полноты.
171
178
  - **После вливания главной ветки набор проверок пересматривается по тому, что ветка везёт
172
179
  теперь.** Вливание меняет состав правки: проверять по тому, что правил автор, — значит
173
180
  проверять половину, а отвечает ветка целиком. Ветка, не тронувшая ни строки показа, прогоняет
@@ -126,6 +126,12 @@ flowchart TD
126
126
  умолчаниями, которые он разрешает. Умолчание образа задаётся в самом образе.
127
127
  - **Образы выкатываются по sha коммита, а не по метке «последний».** Метка в реестре отстаёт
128
128
  от главной ветки, и прод молча возвращается к прежней версии, продолжая отвечать.
129
+ - **Убирает за собой и та машина, которая образы собирает.** Отбор у обеих один — своё имя
130
+ реестра, три последних sha, поднятые контейнеры остаются, — и зовётся он одним сценарием:
131
+ разойдясь, две чистки начали бы оставлять разное, а заметить это нечем. Отличаются они
132
+ хвостом: сервер снимает следом висячие слои и кэш сборки, машина сборки оставляет их себе,
133
+ иначе каждая сборка идёт как первая. Чистка на сборке не ждёт мержа: образ ветки занимает
134
+ столько же места, в реестр не уезжает вовсе и точкой отката не бывает.
129
135
  - **Выкатка убирает за собой старые образы, оставляя три последних sha.** Помеченный sha образ
130
136
  висячим не бывает никогда, и чистка висячего его не касается: за полгода они съедают диск
131
137
  сервера целиком. Три sha — это глубина отката, и меньше брать нельзя: поломка, замеченная
@@ -142,6 +148,15 @@ flowchart TD
142
148
  - **PR проверяется до мержа тем же конвейером, что и главная ветка.** Проверки и сборки
143
149
  образов идут на событии `pull_request`, выкатка — нет: её держит условие по главной ветке у
144
150
  своего задания, а образ PR в реестр не уезжает.
151
+ - **Вершина открытого PR без прогона видна сверкой очереди работ.** Страница PR без прогона
152
+ выглядит так же, как страница с зелёным: цвета у неё нет ни там, ни там, — и вершину, за
153
+ которой прогон не встал, замечали только тем, что открывали список прогонов руками. Сверка
154
+ спрашивает вершину, а не ветку, считает сам факт прогона, а не его цвет, и свежей вершине
155
+ даёт время: между пушем и прогоном проходят минуты.
156
+ - **Черновик при зелёном прогоне на вершине — расхождение сверки.** У черновика кнопка слияния
157
+ заблокирована самим хостингом: зелёная страница PR владельцу ничего не разрешает, а список, в
158
+ котором всё серое, читается как «работа не сделана». Гард снятия черновика сюда не достаёт —
159
+ он судит один ход и молчит, пока ветка везёт папку своей задачи.
145
160
  - **Расхождение прода с главной веткой видно сверкой очереди работ.** Задача уходит из очереди
146
161
  мержем, но мерж — ещё не прод: отказавшая выкатка не трогает ни задачу, ни её колонку, и
147
162
  заметить её неоткуда. Сверка спрашивает последний прогон главной ветки и судит только
@@ -164,6 +179,13 @@ flowchart TD
164
179
  отбивает пуш, а не печатается рядом с ним: напечатанное предупреждение исполнитель читает как
165
180
  разрешение. Дважды подряд правка, прошедшая гейт целиком, была отбита конвейером — и оба раза
166
181
  зелёный гейт был прочитан как «локально всё зелено».
182
+ - **Сверка раскладки стоит в наборе гейта пуша наравне с линтом и сборкой.** Правка, положенная
183
+ в разложенную копию мимо источника, в день, когда её делают, не ломает ничего: дерево
184
+ работает, проверки зелёные, а расхождение видно только тому, кто позовёт сверку сам. Копится
185
+ оно молча и всплывает на чужой работе — раскладка отказывает по правленому файлу целиком и не
186
+ кладёт ни одного другого, так что цену платит тот, кто правил соседний ресурс. Статьёй выше
187
+ эта строка не покрывается: сверки нет в конвейере, а значит нет и шага, который она бы
188
+ закрывала, — в набор она ставится прямо, а не выводится из его полноты.
167
189
  - **После вливания главной ветки набор проверок пересматривается по тому, что ветка везёт
168
190
  теперь.** Вливание меняет состав правки: проверять по тому, что правил автор, — значит
169
191
  проверять половину, а отвечает ветка целиком. Ветка, не тронувшая ни строки показа, прогоняет
@@ -269,6 +291,16 @@ flowchart TD
269
291
  список прогонов ветки смотрится тем же движением, что и пуш — `gh run list --branch <ветка>`.
270
292
  Один такой отказ простоял в списке до мержа, и на него никто не посмотрел: выкатка после
271
293
  мержа отказала ровно тем же.
294
+ - **Прогон, не вставший на пуш, возвращается повтором события, а не разбором ветки.** Замером
295
+ проверены обе законные дороги: и открытие PR, и пуш в уже открытый PR прогон заводят —
296
+ текстовый коммит и учётная запись, которой пушат, тут ни при чём. Пропавшие события пришлись
297
+ на час, когда хостинг отвечал `429` на загрузке действия и `503` на API, а списком прогонов
298
+ «не завёлся» от «не создан» не отличить. Поэтому вершину без прогона называет сверка очереди
299
+ работ, а событие возвращается новым коммитом либо перезакрытием PR.
300
+ - **Красное на шаге подготовки задания — отказ хостинга, а не дефект ветки.** Раннер не смог
301
+ скачать действие чекаута и получил `429 Too Many Requests`; до кода прогон при этом не дошёл
302
+ вовсе. Лечится перезапуском прогона, и от красного по существу отличается тем, на каком шаге
303
+ оно встало: три прогона одного дня упали именно так.
272
304
  - **Контекст `runner` в `env` задания отбивает весь файл конвейера.** Там доступны только
273
305
  `github`, `needs`, `strategy`, `matrix`, `vars`, `secrets` и `inputs`; `runner` появляется на
274
306
  уровне шага, где то же значение приходит переменной окружения. Такой файл не принимается
@@ -279,6 +311,15 @@ flowchart TD
279
311
  пересечения нет, раннер стоит `online` и не берёт ничего, а задания уходят в облако — по
280
312
  состоянию это выглядит настроенным. Владельцу называют выполненное задание с его номером, а
281
313
  не строку состояния.
314
+ - **Раннер на своей машине делает прогон общим ресурсом, и стенд у прогонов один.** Порт,
315
+ имя базы и каталог сборки зашиты в дереве одним значением на всех: два прогона разом
316
+ поднимают два стенда на один порт, и второй падает целиком. Дороже всего не падение, а его
317
+ вид — в отчёте оно выглядит десятком красных спек про экраны, то есть дефектом правки,
318
+ которого нет; три прогона подряд так и упали на трёх ветках, не тронувших кода. Лечится с
319
+ двух сторон сразу: конвейеру объявляется группа очереди на всё дерево, а имена стенда
320
+ читаются из окружения с нынешними значениями в умолчании — иначе прогон и гейт пуша, зовущий
321
+ ту же команду, столкнутся и при очереди. Одной очереди мало, одних имён — тоже: прогоны делят
322
+ ещё диск, кэш сборщика и демон образов.
282
323
  - **Вход в реестр образов из раннера, запущенного службой, отказывает молча.** Служба идёт без
283
324
  сеанса пользователя, а клиент реестра уходит в системный помощник хранения ключей и получает
284
325
  отказ во взаимодействии — задание падает до сборки. Свой каталог настроек с пустым помощником
@@ -167,6 +167,13 @@ flowchart TD
167
167
  отбивает пуш, а не печатается рядом с ним: напечатанное предупреждение исполнитель читает как
168
168
  разрешение. Дважды подряд правка, прошедшая гейт целиком, была отбита конвейером — и оба раза
169
169
  зелёный гейт был прочитан как «локально всё зелено».
170
+ - **Сверка раскладки стоит в наборе гейта пуша наравне с линтом и сборкой.** Правка, положенная
171
+ в разложенную копию мимо источника, в день, когда её делают, не ломает ничего: дерево
172
+ работает, проверки зелёные, а расхождение видно только тому, кто позовёт сверку сам. Копится
173
+ оно молча и всплывает на чужой работе — раскладка отказывает по правленому файлу целиком и не
174
+ кладёт ни одного другого, так что цену платит тот, кто правил соседний ресурс. Статьёй выше
175
+ эта строка не покрывается: сверки нет в конвейере, а значит нет и шага, который она бы
176
+ закрывала, — в набор она ставится прямо, а не выводится из его полноты.
170
177
  - **После вливания главной ветки набор проверок пересматривается по тому, что ветка везёт
171
178
  теперь.** Вливание меняет состав правки: проверять по тому, что правил автор, — значит
172
179
  проверять половину, а отвечает ветка целиком. Ветка, не тронувшая ни строки показа, прогоняет
@@ -70,6 +70,10 @@ flowchart TD
70
70
  семьям, живёт в `common/platform`.
71
71
  - **Основание семейства видит только `util`.** Его зовут все домены семьи, и любая его
72
72
  зависимость становится общей для всех сразу.
73
+ - **Поведение, которого основанию семейства не видно, приходит к нему токеном.** Токен
74
+ объявляется в `util` общего домена, а `data-access` подставляет туда свою реализацию: так
75
+ основание зовёт её, не видя её либы. Расширение границ основания ради одного вызова
76
+ открывает эту либу всем доменам семьи сразу и обратно уже не сужается.
73
77
  - **Домен заводится под предмет, а не под механику.** Механика, общая нескольким доменам,
74
78
  едет в либу, которой она уже видна: у фронта это основание семейства, у бэкенда — слой
75
79
  `util`, перечисленный у каждого домена.
@@ -13,14 +13,14 @@ description: Правило под «Закон о списке записей»
13
13
 
14
14
  ## Как это называется здесь
15
15
 
16
- | В законе | Здесь |
17
- | ------------------------- | ------------------------------------------------------------------------- |
18
- | таблица записей | `rt-table` из `@rt-tools/ui-kit-v2`, вход `[dataSource]` |
19
- | состав и порядок столбцов | `[columnsConfig]`, хранятся по ключу `tableId` |
20
- | карточка на узком экране | ветка `<префикс>-table`, а не своя разметка |
21
- | тулбар | `<префикс>-toolbar` со слотами `vmToolbarLeft` и `vmToolbarRight` |
22
- | выборка | `IList.Query.State` — страница, сортировка, условия отбора, строка поиска |
23
- | панель настройки столбцов | асайд по маршруту `path: 'table-settings'` |
16
+ | В законе | Здесь |
17
+ | ------------------------- | ------------------------------------------------------------------------------- |
18
+ | таблица записей | `rt-table` из `@rt-tools/ui-kit-v2`, вход `[dataSource]` |
19
+ | состав и порядок столбцов | `[columnsConfig]`, хранятся по ключу `tableId` |
20
+ | карточка на узком экране | ветка `<префикс>-table`, а не своя разметка |
21
+ | тулбар | `<префикс>-toolbar` со слотами `<префикс>ToolbarLeft` и `<префикс>ToolbarRight` |
22
+ | выборка | `IList.Query.State` — страница, сортировка, условия отбора, строка поиска |
23
+ | панель настройки столбцов | асайд по маршруту `path: 'table-settings'` |
24
24
 
25
25
  ## Где это лежит
26
26
 
@@ -76,7 +76,7 @@ flowchart TD
76
76
  - **Сортируема та колонка, у чьей ячейки шапки стоит заголовок сортировки.** Отдельного
77
77
  признака рядом со списком столбцов нет, и расходиться нечему.
78
78
  - **Клик по строке открывает запись, а меню — для действий над ней.** Вид нажимаемой строки
79
- даёт `clickable`, активацию мышью и с клавиатуры — `vmTableRow`; клик по кнопке внутри строки
79
+ даёт `clickable`, активацию мышью и с клавиатуры — `<префикс>TableRow`; клик по кнопке внутри строки
80
80
  активацией не считается.
81
81
  - **Доступность действия лежит полем строки, а не вызовом метода компонента.** Метод из шаблона
82
82
  пересчитывался бы на каждой проверке.
@@ -105,7 +105,7 @@ flowchart TD
105
105
 
106
106
  ## Ловушки
107
107
 
108
- - Без `[vmTableRowActionsRowType]` тип `let-row` выводится как `unknown`, и падает только
108
+ - Без `[<префикс>TableRowActionsRowType]` тип `let-row` выводится как `unknown`, и падает только
109
109
  продовая сборка — юниты и дев-сервер проходят.
110
110
  - Прокрутке нужны оба правила вместе: контейнер с `overflow-x`, таблица с
111
111
  `min-width: max-content`. С одним столбцы сжимаются вместо сдвига.
@@ -17,7 +17,7 @@ description: Правило под «Закон об общем коде при
17
17
  | общий пакет | `@rt-tools/utils` — собран без фреймворка, зависит от одного `tslib`, грузится под голым Node |
18
18
  | общая либа проекта | `@<область>/common/util`; её тег входит в набор `UNIVERSAL` и виден всем трём приложениям |
19
19
  | число-настройка | `DEFAULT_PAGE_SIZE`, `MAX_PAGE_SIZE` |
20
- | набор значений | `FILTER_OPERATOR_TYPE_ENUM`, `LIST_SORT_ORDER_ENUM` |
20
+ | набор значений | `EFilterOperatorType`, `EListSortOrder` |
21
21
  | сверка значения с набором | `listSortOrderOf`, `listFilterOperatorOf` |
22
22
  | выборка списка | `IPageModel`, `ISortModel`, `IFilterModel`, `IListState` |
23
23