@rt-tools/agent-kit 0.7.0 → 0.8.1

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.
@@ -8,11 +8,13 @@
8
8
  * видно ни одному из них. Эта проверка отвечает на другой вопрос — «а сколько такого в
9
9
  * дереве сейчас», — и потому смотрит на файл целиком, а не на правку.
10
10
  *
11
- * Признаки те же, что у гарда, и берутся оттуда же: расходиться им нельзя, иначе правка
12
- * проходит гард и падает на гейте. Отличий два. Первое: инвентарь кита не читается гард
13
- * спрашивает диск, потому что отвечает одной правке, а сплошной проверке важно накопленное,
14
- * и пропавший пакет молча обнулял бы сводку. Второе: маркер `native-ok` снимает свою
15
- * строку, а не весь файл.
11
+ * Признаки не лежат здесь: они объявлены наборами по пакетам rt-tools, и дерево называет в
12
+ * настройке проверок те, что берёт. Тот же список читает гард расходиться им нельзя, иначе
13
+ * правка проходит гард и падает на гейте. Свои признаки дерево дописывает своим файлом.
14
+ *
15
+ * Отличий от гарда два. Первое: инвентарь кита не читается — гард спрашивает диск, потому что
16
+ * отвечает одной правке, а сплошной проверке важно накопленное, и пропавший пакет молча
17
+ * обнулял бы сводку. Второе: маркер `native-ok` снимает свою строку и следующую, а не весь файл.
16
18
  *
17
19
  * Накопленное к моменту заведения проверки лежит в tools/reuse-allowlist.json и отказом не
18
20
  * считается: гейт падает на новом расхождении, а старое остаётся видимым числом в сводке.
@@ -24,109 +26,40 @@ import { readFileSync, readdirSync } from 'node:fs';
24
26
  import { join } from 'node:path';
25
27
 
26
28
  import { allowlistOf, CONFIG, ROOT } from './rt-kit-checks.config.mjs';
29
+ import { loadSignals } from './signals.mjs';
27
30
 
28
31
  const ALLOWLIST = allowlistOf('reuse');
29
32
  const SOURCE_ROOTS = CONFIG.sourceRoots;
30
33
  const SKIPPED_DIRS = CONFIG.skippedDirs;
31
34
  const BACKEND_ROOTS = CONFIG.backendRoots;
32
35
 
33
- /**
34
- * Признак расхождения: чем он виден в тексте и на что готовое его меняет. Порядок строк —
35
- * порядок в сводке, поэтому родственные признаки стоят рядом.
36
- */
37
- const SIGNALS = [
38
- {
39
- key: 'input',
40
- ext: '.html',
41
- find: (text) => count(text, /<input\b/g),
42
- instead: 'rt-input / rt-input-number / rt-checkbox / rt-file-input',
43
- },
44
- { key: 'textarea', ext: '.html', find: (text) => count(text, /<textarea\b/g), instead: 'rt-textarea' },
45
- { key: 'select', ext: '.html', find: (text) => count(text, /<select\b/g), instead: 'rt-select / rt-multiselect' },
46
- {
47
- key: 'button',
48
- ext: '.html',
49
- find: (text) => count(withoutKitButtons(text), /<button\b/g),
50
- instead: 'button[rtButton] / rt-icon-button',
51
- },
52
- { key: 'table', ext: '.html', find: (text) => count(text, /<table\b/g), instead: 'rt-table' },
53
- { key: 'dialog', ext: '.html', find: (text) => count(text, /<dialog\b/g), instead: 'rt-dialog' },
54
- { key: 'alert', ext: '.html', find: (text) => count(text, /role="alert"/g), instead: 'rt-message' },
55
-
56
- { key: 'overlay', ext: '.scss', find: fullScreenOverlays, instead: 'rt-dialog / rt-aside / rt-bottom-sheet' },
57
- {
58
- key: 'backdrop',
59
- ext: '.scss',
60
- find: (text) => count(text, /backdrop-filter|background(-color)?: *(rgba\( *0 *, *0 *, *0|rgb\( *0 +0 +0)/g),
61
- instead: 'backdrop рисует rt-dialog',
62
- },
63
- { key: 'z-index', ext: '.scss', find: (text) => count(text, /z-index: *\d{4,}/g), instead: 'слой оверлеев кита уже выше' },
64
- { key: 'spin', ext: '.scss', find: (text) => count(text, /@keyframes[^{]*(spin|rotate|loading)/g), instead: 'rt-spinner' },
65
- {
66
- key: 'shimmer',
67
- ext: '.scss',
68
- find: (text) => count(text, /@keyframes[^{]*(shimmer|skeleton|pulse)/g),
69
- instead: 'rt-skeleton / rt-skeleton-wrapper',
70
- },
71
-
72
- { key: 'inline-template', ext: '.component.ts', find: (text) => count(text, /^ *template: *['"`]/gm), instead: 'шаблон в своём .html' },
73
- // Список стилей открывается своей строкой, а строка с кавычкой идёт следующей
74
- {
75
- key: 'inline-styles',
76
- ext: '.component.ts',
77
- find: (text) => count(text, /^ *styles: *(\[\s*)?['"`\n]/gm),
78
- instead: 'стили в своём .scss',
79
- },
80
- {
81
- key: 'value-accessor',
82
- ext: '.component.ts',
83
- find: (text) => (/ControlValueAccessor|NG_VALUE_ACCESSOR/.test(text) && !/extends VmFormControlBase/.test(text) ? 1 : 0),
84
- instead: 'VmFormControlBase',
85
- },
86
- {
87
- key: 'mapper-base',
88
- ext: '.mapper.ts',
89
- // На бэкенде перевод сущности написан свободными функциями — это долг `Q-S-1`,
90
- // а не место для этой проверки. Где лежит бэкенд, знает настройка дерева: зашитый здесь
91
- // корень молча проверял бы фронтовым мерилом чужой код у всякого, кто держит его иначе.
92
- skip: (path) => BACKEND_ROOTS.some((root) => path.startsWith(root)),
93
- find: (text) => (/class +[A-Za-z0-9_]+Mapper/.test(text) && !/extends BaseMapper/.test(text) ? 1 : 0),
94
- instead: 'BaseMapper и this.typeCast',
95
- },
96
- {
97
- key: 'procedure-mark',
98
- ext: '.procedure.ts',
99
- find: (text) => (/export class/.test(text) && !/@ConnectProcedure/.test(text) ? 1 : 0),
100
- instead: '@ConnectProcedure()',
101
- },
102
- {
103
- key: 'aside-base',
104
- ext: '.component.ts',
105
- onlyNamed: /aside[^/]*\.component\.ts$/,
106
- find: (text) => (/export class/.test(text) && !/extends VmRouteAsideComponent/.test(text) ? 1 : 0),
107
- instead: 'VmRouteAsideComponent',
108
- },
109
- ];
36
+ const SIGNALS = loadSignals(CONFIG.reuse ?? {}, ROOT);
110
37
 
111
38
  function count(text, expression) {
112
39
  return [...text.matchAll(expression)].length;
113
40
  }
114
41
 
115
- /** `button[rtButton]` — предписанный вариант, а подстрока `<button` у него та же */
116
- function withoutKitButtons(text) {
117
- return text.replace(/<button\b[^>]*rtButton[^>]*>/gs, '');
118
- }
119
-
120
- /** Хост, растянутый на весь экран: `inset: 0` либо все четыре стороны в нуле */
121
- function fullScreenOverlays(text) {
122
- if (!/position: *fixed/.test(text)) {
42
+ /**
43
+ * Сколько раз признак виден в тексте.
44
+ *
45
+ * `strip` вычёркивает предписанный вариант до счёта: у кнопки кита та же подстрока `<button`, и
46
+ * без вычёркивания она считалась бы нарушением сама по себе. `all` требует совпадения всех
47
+ * образцов разом — так описан хост, растянутый на весь экран. `cancel` гасит признак целиком:
48
+ * основа уже унаследована, готовое уже позвано.
49
+ */
50
+ function found(signal, text) {
51
+ const body = signal.strip ? text.replace(new RegExp(signal.strip, 'gs'), '') : text;
52
+ if (signal.cancel && new RegExp(signal.cancel).test(body)) {
53
+ return 0;
54
+ }
55
+ if (signal.all?.some((one) => !new RegExp(one).test(body))) {
123
56
  return 0;
124
57
  }
125
- const stretched =
126
- /inset: *0/.test(text) ||
127
- (/(^|[^-])top: *0/.test(text) && /left: *0/.test(text) && /right: *0/.test(text) && /bottom: *0/.test(text));
58
+ if (signal.mode === 'presence') {
59
+ return new RegExp(signal.find).test(body) ? 1 : 0;
60
+ }
128
61
 
129
- return stretched ? 1 : 0;
62
+ return count(body, new RegExp(signal.find, signal.flags ?? 'g'));
130
63
  }
131
64
 
132
65
  function collectFiles(dir) {
@@ -152,14 +85,16 @@ function judged(path) {
152
85
  }
153
86
 
154
87
  /**
155
- * Строка с маркером — осознанное отступление, названное автором. Снимается только она сама:
156
- * весь файл маркер не гасит, иначе один разрешённый случай прикрывал бы соседние.
88
+ * Маркер — осознанное отступление, названное автором. Снимается строка, где он стоит, и та, что
89
+ * идёт следом: в разметке маркер ставится комментарием над кодом, потому что форматировщик
90
+ * разносит длинный тег по строкам и уводит первый атрибут со строки имени тега — признак считает
91
+ * имя тега, а маркер оказывается ниже. Дальше следующей строки маркер не достаёт: весь файл он
92
+ * не гасит, иначе один разрешённый случай прикрывал бы соседние.
157
93
  */
158
94
  function withoutMarked(text) {
159
- return text
160
- .split('\n')
161
- .filter((line) => !line.includes('native-ok'))
162
- .join('\n');
95
+ const lines = text.split('\n');
96
+
97
+ return lines.filter((line, index) => !line.includes('native-ok') && !lines[index - 1]?.includes('native-ok')).join('\n');
163
98
  }
164
99
 
165
100
  const allowlist = JSON.parse(readFileSync(join(ROOT, ALLOWLIST), 'utf8'));
@@ -171,12 +106,13 @@ for (const root of SOURCE_ROOTS) {
171
106
  for (const path of collectFiles(root).filter(judged).sort()) {
172
107
  const text = withoutMarked(readFileSync(join(ROOT, path), 'utf8'));
173
108
  for (const signal of SIGNALS) {
174
- if (!path.endsWith(signal.ext) || signal.skip?.(path) || (signal.onlyNamed && !signal.onlyNamed.test(path))) {
109
+ const skipped = signal.skipBackendRoots && BACKEND_ROOTS.some((root) => path.startsWith(root));
110
+ if (!path.endsWith(signal.ext) || skipped || (signal.onlyNamed && !new RegExp(signal.onlyNamed).test(path))) {
175
111
  continue;
176
112
  }
177
- const found = signal.find(text);
178
- if (found > 0) {
179
- findings.push({ key: `${signal.key} ×${found} @ ${path}`, instead: signal.instead });
113
+ const times = found(signal, text);
114
+ if (times > 0) {
115
+ findings.push({ key: `${signal.key} ×${times} @ ${path}`, instead: signal.instead });
180
116
  }
181
117
  }
182
118
  }
@@ -48,6 +48,14 @@ const DEFAULTS = {
48
48
  * вносится только исходник. Пусто — переносимых текстов дерево не держит.
49
49
  */
50
50
  portableDirs: [],
51
+ /**
52
+ * Признаки единообразия: какие наборы дерево берёт и где лежат его собственные.
53
+ *
54
+ * Наборы режутся по пакетам, чьё готовое они называют, и дерево объявляет только те, что
55
+ * ставит: признак о готовом из пакета, которого в дереве нет, отвечает ложно. Пусто —
56
+ * признаков нет вовсе, и гард с проверкой об этом говорят, а не молчат.
57
+ */
58
+ reuse: { bundles: [], signals: '' },
51
59
  /** Где лежат спеки доменов; пусто — их в дереве нет, и сверка спеков не запускается. */
52
60
  specsDir: 'docs/specs',
53
61
  tasksDir: 'docs/tasks',
@@ -0,0 +1,19 @@
1
+ {
2
+ "about": "Признаки самого фреймворка: ничьих имён не называют и верны любому дереву на Angular.",
3
+ "signals": [
4
+ { "key": "inline-template", "ext": ".component.ts", "find": "^ *template: *['\"`]", "flags": "gm", "instead": "шаблон в своём .html" },
5
+ {
6
+ "key": "inline-styles",
7
+ "ext": ".component.ts",
8
+ "find": "^ *styles: *(\\[\\s*)?['\"`\\n]",
9
+ "flags": "gm",
10
+ "instead": "стили в своём .scss"
11
+ },
12
+ {
13
+ "key": "decorator-io",
14
+ "ext": ".ts",
15
+ "find": "@(Input|Output|ViewChild|ViewChildren|ContentChild)\\(",
16
+ "instead": "input(), output(), viewChild(), contentChild()"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "about": "Признаки пакета @rt-tools/core: директивы BEM, доступ к среде и хранилищу браузера.",
3
+ "signals": [
4
+ { "key": "class-binding", "ext": ".html", "find": "\\[ngClass\\]|\\[class\\.", "instead": "директивы rtBlock, rtElem, [rtMod]" },
5
+ {
6
+ "key": "raw-storage",
7
+ "ext": ".ts",
8
+ "find": "(window\\.)?(local|session)Storage\\.",
9
+ "instead": "службы хранилища @rt-tools/core"
10
+ },
11
+ { "key": "raw-media-query", "ext": ".ts", "find": "matchMedia\\(", "instead": "BreakpointsService" },
12
+ {
13
+ "key": "raw-window",
14
+ "ext": ".ts",
15
+ "find": "(^|[^.\\w])(window|document)\\.",
16
+ "instead": "PlatformService и токены среды @rt-tools/core"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "about": "Признаки пакета @rt-tools/store: своё хранилище состояния вместо базового.",
3
+ "signals": [
4
+ {
5
+ "key": "store-base",
6
+ "ext": ".service.ts",
7
+ "mode": "presence",
8
+ "scope": "whole",
9
+ "find": "new BehaviorSubject|private +readonly +state *=",
10
+ "cancel": "extends +Base(Async)?StoreService",
11
+ "instead": "BaseStoreService / BaseAsyncStoreService"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "about": "Признаки пакета @rt-tools/ui-kit-v2: нативный контрол там, где кит везёт свой.",
3
+ "signals": [
4
+ { "key": "input", "ext": ".html", "find": "<input\\b", "instead": "rt-input / rt-input-number / rt-file-input / rt-checkbox" },
5
+ { "key": "textarea", "ext": ".html", "find": "<textarea\\b", "instead": "rt-textarea" },
6
+ { "key": "select", "ext": ".html", "find": "<select\\b", "instead": "rt-select / rt-multiselect" },
7
+ {
8
+ "key": "button",
9
+ "ext": ".html",
10
+ "strip": "<button\\b[^>]*rtButton[^>]*>",
11
+ "find": "<button\\b",
12
+ "instead": "button[rtButton] / rt-icon-button / rt-split-button"
13
+ },
14
+ { "key": "table", "ext": ".html", "find": "<table\\b", "instead": "rt-table" },
15
+ { "key": "dialog", "ext": ".html", "find": "<dialog\\b", "instead": "rt-dialog" },
16
+ { "key": "alert", "ext": ".html", "find": "role=\"alert\"", "instead": "rt-message / rt-toast" },
17
+ {
18
+ "key": "overlay-inset",
19
+ "ext": ".scss",
20
+ "mode": "presence",
21
+ "find": "position: *fixed",
22
+ "all": ["inset: *0"],
23
+ "instead": "rt-dialog / rt-aside / rt-bottom-sheet"
24
+ },
25
+ {
26
+ "key": "overlay-sides",
27
+ "ext": ".scss",
28
+ "mode": "presence",
29
+ "find": "position: *fixed",
30
+ "all": ["(^|[^-])top: *0", "left: *0", "right: *0", "bottom: *0"],
31
+ "instead": "rt-dialog / rt-aside / rt-bottom-sheet"
32
+ },
33
+ {
34
+ "key": "backdrop",
35
+ "ext": ".scss",
36
+ "find": "backdrop-filter|background(-color)?: *(rgba\\( *0 *, *0 *, *0|rgb\\( *0 +0 +0)",
37
+ "instead": "подложку рисует rt-dialog"
38
+ },
39
+ { "key": "z-index", "ext": ".scss", "find": "z-index: *\\d{4,}", "instead": "слой перекрытий кита уже выше" },
40
+ { "key": "spin", "ext": ".scss", "find": "@keyframes[^{]*(spin|rotate|loading)", "instead": "rt-spinner" },
41
+ {
42
+ "key": "shimmer",
43
+ "ext": ".scss",
44
+ "find": "@keyframes[^{]*(shimmer|skeleton|pulse)",
45
+ "instead": "rt-skeleton / rt-skeleton-wrapper"
46
+ }
47
+ ]
48
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "about": "Признаки пакета @rt-tools/ui-kit: нативный контрол там, где кит везёт свой.",
3
+ "signals": [
4
+ { "key": "input", "ext": ".html", "find": "<input\\b", "instead": "rtui-dynamic-input / rtui-checkbox / rtui-toggle" },
5
+ { "key": "button", "ext": ".html", "find": "<button\\b", "instead": "rtui-button / rtui-multi-button" },
6
+ { "key": "table", "ext": ".html", "find": "<table\\b", "instead": "rtui-table / rtui-table-container" },
7
+ { "key": "dialog", "ext": ".html", "find": "<dialog\\b", "instead": "rtui-modal / rtui-aside-panel" },
8
+ { "key": "alert", "ext": ".html", "find": "role=\"alert\"", "instead": "rtui-snack-bar / rtui-info-badge" },
9
+ { "key": "spin", "ext": ".scss", "find": "@keyframes[^{]*(spin|rotate|loading)", "instead": "rtui-spinner" }
10
+ ]
11
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "about": "Признаки пакета @rt-tools/utils: основа перевода сущности и приведение типов.",
3
+ "signals": [
4
+ {
5
+ "key": "mapper-base",
6
+ "ext": ".mapper.ts",
7
+ "mode": "presence",
8
+ "scope": "whole",
9
+ "skipBackendRoots": true,
10
+ "find": "class +[A-Za-z0-9_]+Mapper",
11
+ "cancel": "extends +BaseMapper",
12
+ "instead": "BaseMapper и this.typeCast"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Наборы признаков единообразия: что дерево объявило своим и что при этом читается.
3
+ *
4
+ * Признак не лежит в коде проверки. Пакет режет признаки по своим пакетам — у кита свои, у
5
+ * хранилища свои, — а дерево называет в настройке проверок те, что берёт. Набор, который дерево
6
+ * не назвало, не читается вовсе: признак о готовом из пакета, которого в дереве нет, отвечает
7
+ * ложно ровно так же, как имя чужого приложения.
8
+ *
9
+ * Тот же список читает гард на правке. Общий файл — единственное, чем гард на оболочке и
10
+ * проверка на JS могут быть связаны: расходиться им нельзя, иначе правка проходит гард и падает
11
+ * на гейте.
12
+ */
13
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
14
+ import { dirname, join } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ /** Наборы лежат рядом с этим файлом: он и сам ресурс пакета, и раскладывается вместе с ними. */
18
+ export const BUNDLES_DIR = join(dirname(fileURLToPath(import.meta.url)), 'signals');
19
+
20
+ export function bundleNames() {
21
+ if (!existsSync(BUNDLES_DIR)) {
22
+ return [];
23
+ }
24
+
25
+ return readdirSync(BUNDLES_DIR)
26
+ .filter((name) => name.endsWith('.json'))
27
+ .map((name) => name.slice(0, -'.json'.length))
28
+ .sort();
29
+ }
30
+
31
+ /**
32
+ * Разбор набора: шапка раскладки снимается до JSON.
33
+ *
34
+ * Раскладка ставит её первой строкой и комментирует незнакомое расширение решёткой — в JSON
35
+ * комментария нет, и без снятия разбор падает на первом же символе.
36
+ */
37
+ function parseSignals(text) {
38
+ return JSON.parse(text.replace(/^#[^\n]*\n/, '')).signals ?? [];
39
+ }
40
+
41
+ function readBundle(name) {
42
+ const path = join(BUNDLES_DIR, `${name}.json`);
43
+ if (!existsSync(path)) {
44
+ const known = bundleNames().join(', ') || 'ни одного';
45
+ throw new Error(`набор признаков «${name}» при пакете не найден; есть: ${known}`);
46
+ }
47
+
48
+ return parseSignals(readFileSync(path, 'utf8'));
49
+ }
50
+
51
+ /**
52
+ * Признаки объявленных наборов, а поверх них — свои признаки дерева.
53
+ *
54
+ * Совпавший ключ замещает пакетный: дерево вправе сказать о своём готовом точнее, чем пакет,
55
+ * который его не видел. Файла своих признаков может не быть — это не отказ: пропуск дешевле
56
+ * остановки работы, и объявляют его раньше, чем заводят.
57
+ */
58
+ export function loadSignals(config, root) {
59
+ const declared = config.bundles ?? [];
60
+ const signals = declared.flatMap(readBundle);
61
+
62
+ const ownPath = config.signals ? join(root, config.signals) : null;
63
+ const own = ownPath && existsSync(ownPath) ? parseSignals(readFileSync(ownPath, 'utf8')) : [];
64
+
65
+ const byKey = new Map(signals.map((signal) => [signal.key, signal]));
66
+ for (const signal of own) {
67
+ byKey.set(signal.key, signal);
68
+ }
69
+
70
+ return [...byKey.values()];
71
+ }
@@ -220,20 +220,22 @@ rt_task_state_default() {
220
220
  # Образцы узкие намеренно: гард сверяет только НОВЫЙ текст, и широкий образец отбивал бы
221
221
  # правку, которая ничего нового не заводит.
222
222
  rt_reinvented_in_default() {
223
+ # Четыре поля через табуляцию: над чем, образец, образец отмены, чем заменить. Пустое поле
224
+ # пишется пустым — их читают по одному, и схлопывание третьего уносило совет в отмену.
223
225
  case "$1" in
224
226
  *.ts)
225
- printf '%s\t%s\n' '@(Input|Output|ViewChild|ViewChildren|ContentChild)\(' 'input(), output(), viewChild(), contentChild() реактивный вход и выход'
226
- printf '%s\t%s\n' 'get [a-zA-Z]+\(\)[[:space:]]*(:|\{)' 'computed(): производное значение сигналом, а не геттером'
227
- ;;
228
- *.html)
229
- printf '%s\t%s\n' '\[ngClass\]|\[class\.' 'директивы класса кита'
227
+ printf '%s\t%s\t%s\t%s\n' 'added' 'get [a-zA-Z]+\(\)[[:space:]]*(:|\{)' '' 'computed(): производное значение сигналом, а не геттером'
230
228
  ;;
231
229
  *.scss)
232
- printf '%s\t%s\n' '#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?\b' 'токен оформления вместо записанного цвета'
230
+ printf '%s\t%s\t%s\t%s\n' 'added' '#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?\b' '' 'токен оформления вместо записанного цвета'
233
231
  ;;
234
232
  esac
235
233
  }
236
234
 
235
+ # Где лежат наборы признаков единообразия — они раскладываются рядом с проверками, а раскладка
236
+ # проверок у каждого дерева своя. Читают их и гард на правке, и сплошная сверка.
237
+ RT_REUSE_SIGNALS_DIR="${RT_REUSE_SIGNALS_DIR:-tools/signals}"
238
+
237
239
  # Где якорь для спек не требуется. Витрина, корневая разметка и сборка — общее у всех деревьев;
238
240
  # своё дерево дописывает надстройкой.
239
241
  RT_QA_SKIP_RE="${RT_QA_SKIP_RE:-/node_modules/|/dist/|\.stories\.html\$|/src/index\.html\$}"
@@ -132,8 +132,90 @@ has_re() {
132
132
  printf '%s' "$1" | RT_RE="$2" perl -0777 -ne 'exit(/$ENV{RT_RE}/s ? 0 : 1)' 2>/dev/null
133
133
  }
134
134
 
135
+ # Что дерево объявило своим: наборы признаков и файл собственных. Настройка проверок — та же,
136
+ # что читает сплошная проверка; каталог наборов называет профиль дерева, потому что раскладка
137
+ # проверок у каждого дерева своя.
138
+ rt_checks_json="${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/checks.json"
139
+ rt_bundles=''
140
+ rt_own_signals=''
141
+ if [ -f "$rt_checks_json" ]; then
142
+ rt_bundles="$(jq -r '.reuse.bundles[]? // empty' "$rt_checks_json" 2>/dev/null | tr '\n' ' ')"
143
+ own="$(jq -r '.reuse.signals // empty' "$rt_checks_json" 2>/dev/null)"
144
+ [ -n "$own" ] && rt_own_signals="${CLAUDE_PROJECT_DIR:-.}/$own"
145
+ fi
146
+ rt_signals_dir="${CLAUDE_PROJECT_DIR:-.}/${RT_REUSE_SIGNALS_DIR:-tools/signals}"
147
+
148
+ # Признаки объявленных наборов: те же файлы читает сплошная проверка. Ключ признака совпал с
149
+ # ключом дерева — побеждает дерево: оно видит своё готовое, а пакет его не видел.
150
+ signals_json() {
151
+ [ -n "$rt_signals_dir" ] || return 0
152
+ [ -d "$rt_signals_dir" ] || return 0
153
+ # Шапка раскладки снимается до разбора: в JSON комментария нет, и с ней разбор падает.
154
+ for name in $rt_bundles; do
155
+ file="$rt_signals_dir/$name.json"
156
+ [ -f "$file" ] && grep -v '^# rt-kit ' "$file" | jq -c '.signals[]?' 2>/dev/null
157
+ done
158
+ [ -n "$rt_own_signals" ] && [ -f "$rt_own_signals" ] && grep -v '^# rt-kit ' "$rt_own_signals" | jq -c '.signals[]?' 2>/dev/null
159
+ }
160
+
161
+ # Поля читаются по одному, а не разбором строки: таб в `IFS` — пробельный разделитель, и пустое
162
+ # поле в середине схлопывается, из-за чего совет уезжает в образец отмены и гасит признак молча.
163
+ field() { printf '%s' "$1" | jq -r "$2 // empty" 2>/dev/null; }
164
+
135
165
  found=''
136
- while IFS="$(printf '\t')" read -r scope pattern cancel replacement; do
166
+ signals_seen=0
167
+ while IFS= read -r signal; do
168
+ [ -z "$signal" ] && continue
169
+ signals_seen=1
170
+ ext="$(field "$signal" '.ext')"
171
+ case "$ext" in
172
+ '') ;;
173
+ *) case "$path" in *"$ext") ;; *) continue ;; esac ;;
174
+ esac
175
+ only_named="$(field "$signal" '.onlyNamed')"
176
+ if [ -n "$only_named" ]; then
177
+ printf '%s' "${path##*/}" | grep -qE "$only_named" || continue
178
+ fi
179
+
180
+ case "$(field "$signal" '.scope')" in
181
+ whole) text="$whole" ;;
182
+ *) text="$added" ;;
183
+ esac
184
+
185
+ pattern="$(field "$signal" '.find')"
186
+ [ -z "$pattern" ] && continue
187
+ strip="$(field "$signal" '.strip')"
188
+ [ -n "$strip" ] && text="$(printf '%s' "$text" | RT_RE="$strip" perl -0777 -pe 's/$ENV{RT_RE}//gs' 2>/dev/null)"
189
+
190
+ has_re "$text" "$pattern" || continue
191
+
192
+ cancel="$(field "$signal" '.cancel')"
193
+ [ -n "$cancel" ] && has_re "$text" "$cancel" && continue
194
+
195
+ skip_signal=''
196
+ while IFS= read -r one; do
197
+ [ -z "$one" ] && continue
198
+ has_re "$text" "$one" || skip_signal=1
199
+ done <<ALL
200
+ $(printf '%s' "$signal" | jq -r '.all[]? // empty' 2>/dev/null)
201
+ ALL
202
+ [ -n "$skip_signal" ] && continue
203
+
204
+ found="${found}
205
+ - $(field "$signal" '.instead')"
206
+ done <<EOF
207
+ $(signals_json | jq -s -c 'reduce .[] as $one ({}; .[$one.key] = $one) | .[]' 2>/dev/null)
208
+ EOF
209
+
210
+ # Функция профиля остаётся вторым источником: деревья её уже написали. Поля читаются построчно,
211
+ # по тем же четырём колонкам, что объявлены выше.
212
+ while IFS= read -r line; do
213
+ [ -z "$line" ] && continue
214
+ signals_seen=1
215
+ scope="$(printf '%s' "$line" | cut -f1)"
216
+ pattern="$(printf '%s' "$line" | cut -f2)"
217
+ cancel="$(printf '%s' "$line" | cut -f3)"
218
+ replacement="$(printf '%s' "$line" | cut -f4)"
137
219
  [ -z "$pattern" ] && continue
138
220
  case "$scope" in
139
221
  whole) text="$whole" ;;
@@ -147,6 +229,13 @@ done <<EOF
147
229
  $(rt_reinvented_in "$path" 2>/dev/null)
148
230
  EOF
149
231
 
232
+ # Гард без единого признака неотличим от гарда, которому нечего отбивать. Правку он пропускает —
233
+ # останавливать работу за ненастроенное дерево не за что, — но говорит, чем это настраивается.
234
+ if [ "$signals_seen" = 0 ]; then
235
+ printf '%s\n' 'reuse-first-guard: признаков нет — объявите наборы ключом `reuse.bundles` в настройке проверок' >&2
236
+ exit 0
237
+ fi
238
+
150
239
  [ -z "$found" ] && exit 0
151
240
 
152
241
  reason="BLOCKED: это уже написано. Файл: ${path##*/}
@@ -45,6 +45,36 @@ grep -rn "<похожий приём>" libs/admin libs/site --include='*.html' |
45
45
  Спрашивается до того, как написан первый файл. То же относится к своей базе и к своему
46
46
  инлайновому стилю.
47
47
 
48
+ ## Своё готовое объявляется признаком
49
+
50
+ Заведённое с одобрения владельца видит только тот, кто о нём знает. Чтобы следующий его не
51
+ обошёл, дерево дописывает признак в свой файл признаков — тот, что назван ключом
52
+ `reuse.signals` в настройке проверок:
53
+
54
+ ```json
55
+ {
56
+ "key": "<короткое имя признака>",
57
+ "ext": ".ts",
58
+ "mode": "presence",
59
+ "scope": "whole",
60
+ "find": "implements +<интерфейс, который закрывает основа>",
61
+ "cancel": "extends +<основа>",
62
+ "instead": "<основа> — она уже держит <что именно>"
63
+ }
64
+ ```
65
+
66
+ Область объявляется явно. `whole` читает правку вместе с содержимым файла и берётся там, где
67
+ признак судит устройство класса: наследование и метка объявлены один раз и в точечную правку не
68
+ попадают, поэтому признак без `whole` молчит всегда. `added` читает только добавленный текст и
69
+ берётся там, где признак судит саму строку — сырой тег, свой стиль.
70
+
71
+ Отмена `cancel` обязательна везде, где готовое можно позвать законно: без неё признак отбивает и
72
+ того, кто основу уже унаследовал. Пустой её не оставляют — признак без отмены объявляется без
73
+ этого поля вовсе.
74
+
75
+ Ключ, совпавший с пакетным, замещает его: дерево вправе сказать о своём готовом точнее, чем
76
+ пакет, который его не видел.
77
+
48
78
  ## Разовое отступление объявляется маркером
49
79
 
50
80
  ```html
@@ -52,9 +82,18 @@ grep -rn "<похожий приём>" libs/admin libs/site --include='*.html' |
52
82
  <input type="tel" qa-dataid="phone-input" />
53
83
  ```
54
84
 
55
- Маркер `native-ok` ставится в той же строке и объясняет, **чего именно нет в ките**. «Эти
56
- строки были здесь раньше» причиной не считается: гард вычёркивает из проверяемого текста то,
57
- что уже лежит в файле, поэтому отказ означает новый текст.
85
+ Маркер `native-ok` объясняет, **чего именно нет в ките**. «Эти строки были здесь раньше»
86
+ причиной не считается: гард вычёркивает из проверяемого текста то, что уже лежит в файле,
87
+ поэтому отказ означает новый текст.
88
+
89
+ Стоит он комментарием строкой выше кода или в самой строке — снимаются обе. В разметке годится
90
+ только первое: форматировщик разносит тег, у которого атрибуты не влезли в предел ширины, по
91
+ строкам, и первый атрибут всегда уезжает на строку ниже имени тега. Признак считает имя тега,
92
+ то есть первую строку, а маркер, поставленный атрибутом, оказывается на второй и не снимает
93
+ ничего. Короткий тег форматировщик не трогает — и маркер работает ровно до тех пор, пока к тегу
94
+ не добавили ещё один атрибут.
95
+
96
+ Дальше следующей строки маркер не достаёт: он снимает свой случай, а не блок вокруг себя.
58
97
 
59
98
  Сверка идёт без отступов — при переезде блок меняет отступ, оставаясь тем же кодом.
60
99
 
@@ -17,7 +17,7 @@ description: Правило под «Закон о единообразии пр
17
17
  | готовое | компоненты кита `@rt-tools/ui-kit-v2` с префиксом `rt-` — сегодня их больше семидесяти — и базовые классы без селектора, наследуемые в `@Component` экрана |
18
18
  | раскладка страниц, форм и окон | `apps/<app>/src/styles/`, применяется директивами BEM |
19
19
  | оформление части приложения | файл `.scss` рядом с компонентом |
20
- | отступление, решённое владельцем | маркер `native-ok` в той же строке с объяснением |
20
+ | отступление, решённое владельцем | маркер `native-ok` с объяснением комментарием строкой выше или в самой строке |
21
21
 
22
22
  ## Где это лежит
23
23
 
@@ -46,9 +46,12 @@ description: Правило под «Закон о единообразии пр
46
46
  раз в общем слое приложения, а экран её только применяет теми же директивами BEM.
47
47
  - **Готовое расширяется, а не клонируется рядом.** Недостающий вариант заводится в ките или в
48
48
  базовом классе, и его видят остальные экраны.
49
- - **Накопленное до гарда сосчитано сплошной проверкой и в список только не растёт.** Признаки
50
- у неё те же, что у гарда, а смотрит она файл целиком: гейт падает на новом месте, старое
51
- остаётся числом в сводке.
49
+ - **Накопленное до гарда сосчитано сплошной проверкой и в список только не растёт.** Признаки у
50
+ неё те же, что у гарда, потому что оба читают одни и те же объявленные наборы, а смотрит она
51
+ файл целиком: гейт падает на новом месте, старое остаётся числом в сводке.
52
+ - **Признаки объявляются деревом, а не зашиты в проверку.** Дерево берёт из rt-tools не всё, и
53
+ признак о готовом из пакета, которого здесь нет, отвечает ложно ровно так же, как имя чужого
54
+ приложения.
52
55
 
53
56
  ## Чего из закона здесь нет
54
57
 
@@ -67,16 +70,24 @@ description: Правило под «Закон о единообразии пр
67
70
 
68
71
  ## Признаки, по которым видно, что готовое обошли
69
72
 
70
- - в шаблоне фичи стоит нативный `<input>`, `<button>`, `<select>`, `<table>` или `<dialog>`;
71
- - отказ или предупреждение собраны руками свой `role="alert"` вместо `rt-message`;
72
- - в `.scss` фичи появились `position: fixed` на весь экран, свой backdrop, `z-index` от тысячи,
73
- `@keyframes` вращения или мерцания;
74
- - в файле стилей экрана объявлена раскладка`display: flex` с `gap` и `padding` на `:host`;
75
- - компонент реализует `ControlValueAccessor` сам, а не наследует `RtFormControlBase`;
76
- - имя файла кончается на `-button`, `-input`, `-dialog`, `-spinner` или `-table` вне
77
- кита;
78
- - маппер переводит поля вручную, без `BaseMapper`;
79
- - процедура бэкенда объявлена без `@ConnectProcedure()`.
73
+ Списком их здесь нет: признак это данные, а не текст правила и не код проверки. Наборы лежат
74
+ при пакете, по файлу на пакет rt-tools, и в каждом только то, что везёт он сам. Дерево называет
75
+ нужные ему наборы ключом `reuse.bundles` в настройке проверок и дописывает свои признаки файлом,
76
+ названным ключом `reuse.signals`; совпавший ключ замещает пакетный, новый дописывается. Гард на
77
+ правке и сплошная проверка читают отсюда обаразойтись им нечем.
78
+
79
+ Что бывает признаком:
80
+
81
+ - нативный контрол в шаблоне там, где кит везёт свой;
82
+ - отказ, предупреждение или ожидание, собранные руками вместо готового;
83
+ - наложение поверх страницы, объявленное своими стилями, — подложка, порядок слоёв, вращение;
84
+ - раскладка, объявленная в файле стилей экрана, а не применённая директивами из общего слоя;
85
+ - своя реализация того, что даёт основа кита, вместо наследования;
86
+ - имя файла, повторяющее имя китового примитива, вне кита.
87
+
88
+ Признак называет свою область: наследование основы и метка класса в точечную правку не попадают,
89
+ и признак, судящий их по добавленному тексту, молчит всегда. Признак называет и отмену — образец,
90
+ при котором он не срабатывает: основа уже унаследована, готовое уже позвано.
80
91
 
81
92
  ## Ловушки
82
93
 
@@ -90,3 +101,10 @@ description: Правило под «Закон о единообразии пр
90
101
  отступ, оставаясь тем же кодом.
91
102
  - Ответ, данный до чтения образца, образец отменяет: согласованная форма выборки списка
92
103
  переигрывалась вместе с контрактом через два вопроса после того, как была принята.
104
+ - **Набор объявляется по тому, что дерево потребляет, а не по тому, что пакет везёт.** Дерево, в
105
+ котором кит написан, а не позван, объявив его набор, получает советы звать кит на файлах самого
106
+ кита: признак верен, но обращён не туда. Такое дерево объявляет только те наборы, чьё готовое
107
+ оно берёт со стороны.
108
+ - **Гард, не получивший ни одного признака, неотличим от гарда, которому нечего отбивать.** Он
109
+ говорит об этом сам, и первая же правка в дереве без объявленных наборов это показывает.
110
+ Молчание проверки признаком порядка не считается — сначала смотрят, есть ли ей чем судить.
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: agent-kit-extend
3
+ description: Готовые примеры того, как дерево дописывает своё поверх пакета правил — надстройка над разложенным текстом, своя ветка карты гейта, своя функция профиля, свой признак единообразия, свой закон и правило. Брать, когда пакетный текст говорит не то, что верно здесь, гейт требует не то правило, гард судит не по тем путям, или своё поведение надо дописать, не трогая пакет. Где что настраивается и как устроена раскладка — скил agent-kit; форма нового скила — write-a-skill.
4
+ ---
5
+
6
+ # Как дописать своё поверх пакета
7
+
8
+ Скил `agent-kit` называет, **где** настраивается каждый род правки. Здесь — **как** выглядит
9
+ сама правка, на готовых примерах, и чем каждая проверяется.
10
+
11
+ Одно правило общее для всех пяти: разложенный файл не правится на месте. Он несёт шапку
12
+ `rt-kit v… · <ресурс> · <сумма>`, правка в нём теряется на следующей раскладке и до тех пор
13
+ выглядит применённой, а `sync` на такой файл отказывает вместо того, чтобы переписать молча.
14
+
15
+ ## Когда брать
16
+
17
+ - Пакетный текст говорит не то, что верно здесь: имена, пути, приёмы этого дерева.
18
+ - Гейт требует под файл не то правило — или молчит там, где правило есть.
19
+ - Гард судит не по тем путям: чужие корни, свой набор проверок, своя форма ветки.
20
+ - Заводится своё — признак единообразия, проверка, закон с правилом, — чего пакет не везёт.
21
+
22
+ ## Текст: надстройка сливается по разделам
23
+
24
+ Файл кладётся в `overrides/<идентификатор ресурса>` — путь повторяет ресурс один в один:
25
+ `rules/testing.md` надстраивается файлом `rules/testing.md`.
26
+
27
+ ```markdown
28
+ ## Ловушки этого дерева
29
+
30
+ - **Снимок дерева правится тем же коммитом, что и объявление.** Иначе установка у соседа
31
+ ставит не то, что стоит здесь.
32
+ ```
33
+
34
+ | Заголовок в надстройке | Что происходит |
35
+ | ---------------------- | ------------------------- |
36
+ | есть у пакета | раздел замещается целиком |
37
+ | нет у пакета | дописывается в конец |
38
+ | есть, но тело пустое | раздел пакета снимается |
39
+
40
+ **Замещение — целиком, и это главная ловушка.** Свой пункт, дописанный под пакетным заголовком
41
+ `## Ловушки`, уносит все пакетные пункты этого раздела разом, и пропажу не видно ничем: файл
42
+ выглядит собранным. Поэтому заголовок в примере выше свой. Пакетный заголовок берут только
43
+ тогда, когда пакетный текст здесь неверен и его правда надо снять.
44
+
45
+ Проверяется раскладкой: `sync`, затем `sync --check` — и глазами по разложенному файлу, на
46
+ месте ли пакетные разделы.
47
+
48
+ От ресурса целиком отказываются не здесь, а списком `skip` в конфиге: надстройка правит текст,
49
+ `skip` отменяет файл.
50
+
51
+ ## Гейт: своя ветка решает раньше умолчания
52
+
53
+ Гейт спрашивает `skill_for` — она печатает имя правила или молчит. Молчание значит «правила на
54
+ это нет», и правка проходит.
55
+
56
+ ```bash
57
+ skill_for() {
58
+ kind="$1"; target="$2"; written="$3"
59
+
60
+ case "$kind" in
61
+ edit)
62
+ case "$target" in
63
+ # Частное — всегда раньше общего: файл истории не файл компонента.
64
+ *.stories.ts) printf '%s\n' 'showcase'; return 0 ;;
65
+ esac
66
+ ;;
67
+ esac
68
+
69
+ # Всё остальное разбирает умолчание пакета — иначе оно теряется целиком.
70
+ command -v skill_for_default >/dev/null 2>&1 && skill_for_default "$kind" "$target" "$written"
71
+
72
+ return 0
73
+ }
74
+ ```
75
+
76
+ Две вещи здесь обязательны. **Порядок веток:** первое совпадение выигрывает, и общая ветка,
77
+ поставленная выше частной, съедает её молча. **Вызов умолчания:** объявив функцию заново и не
78
+ позвав `_default`, дерево остаётся без всех пакетных веток сразу — а выглядит это как «гейт
79
+ перестал требовать правила».
80
+
81
+ Проверяется сценариями гейта из набора пакета: они гоняют карту, ничего не раскладывая.
82
+
83
+ ## Гард: профиль отвечает за имена и команды
84
+
85
+ Тем же приёмом объявляются функции профиля — что гонять перед пушем, какой документ едет парой,
86
+ чем линтуется файл, что здесь считается кодом приложения, какая форма ветки законна.
87
+
88
+ ```bash
89
+ # Какой документ обязан ехать тем же коммитом. Печатает образец пути или молчит.
90
+ rt_docs_pair_for() {
91
+ case "$1" in
92
+ *.spec.ts) return 0 ;;
93
+ libs/kit/src/*/*.component.ts) printf '%s' "${1%/*}/CONTEXT\.md" ;;
94
+ esac
95
+ }
96
+ ```
97
+
98
+ Печатается **образец**, а не путь: гард сверяет им состав коммита. Умолчание зовётся так же —
99
+ `rt_docs_pair_for_default "$@"` — везде, где своё правило случай не закрыло.
100
+
101
+ Путь приходит от корня дерева. Признак по подстроке вида `*/projects/*` совпадает и с чужим
102
+ каталогом за пределами репозитория — так запись в домашний каталог была отбита требованием
103
+ замысла, к ней не относящимся.
104
+
105
+ ## Признаки и проверки: данные, а не код
106
+
107
+ Признак единообразия — данные. Дерево называет наборы пакета, чьё готовое оно берёт, и
108
+ дописывает свои файлом; совпавший ключ замещает пакетный.
109
+
110
+ ```json
111
+ {
112
+ "sourceRoots": ["apps", "libs"],
113
+ "reuse": { "bundles": ["kit"], "signals": "tools/signals/own.json" }
114
+ }
115
+ ```
116
+
117
+ Объект сливается ключ за ключом: назвав один ключ раздела, дерево не теряет соседних. **Список
118
+ — наоборот, замещается целиком**, и это ловушка: назвав `sourceRoots`, дерево получает ровно
119
+ названное, а не пакетные корни плюс свои. «Дописать в список» и «убрать из списка» в этой записи
120
+ неразличимы, поэтому список всегда пишется полностью.
121
+
122
+ Набор объявляется по тому, что дерево **потребляет**. Дерево, в котором кит написан, а не
123
+ позван, объявив его набор, получит советы звать кит на файлах самого кита: признак верен, но
124
+ обращён не туда.
125
+
126
+ ## Свой закон и правило
127
+
128
+ Пакет везёт слой правил, но не запрещает свой. Закон дерева ложится рядом с пакетными, правило
129
+ под него — среди скилов, и связь идёт через шапку: у правила `law:` с именем закона, у паттерна
130
+ `rule:` с именем правила. Имя, которому ничего не отвечает, отбивает сверку связности.
131
+
132
+ Утверждение правила получает строку в спутнике `implementation.md` — привязку к файлу и символу.
133
+ Утверждение, которому места в коде не нашлось, в проверяемый раздел не ставится: ему место в
134
+ «Ловушках» прозой.
135
+
136
+ Проверяется сверкой спеков — до пуша.
137
+
138
+ ## Частые промахи
139
+
140
+ - **Правка на месте вместо надстройки.** Разложенный файл узнаётся по шапке, а не по каталогу:
141
+ раскладка ложится в те же `tools/` и `.claude/`, где лежит своё.
142
+ - **Свой пункт дописан к пакетному заголовку** — пакетные пункты этого раздела ушли молча.
143
+ - **Функция объявлена заново без вызова `_default`** — вместе со своим случаем потеряны все
144
+ пакетные.
145
+ - **Общая ветка карты стоит выше частной** — частная не выполняется никогда.
146
+ - **Замена по шаблону в файле оболочки** — `case` теряет свою `esac`, и гард с ошибкой синтаксиса
147
+ отвечает ненулевым кодом, то есть «правка отбита». После правки — `bash -n`.
148
+ - **Правка ресурса пакета без сборки.** Строка запуска читает собранное, а не исходники:
149
+ порядок всегда один — правка, сборка, `sync`.
@@ -19,19 +19,23 @@ description: Переносимый слой правил агента — за
19
19
 
20
20
  ## Где что настраивается
21
21
 
22
- | Что меняешь | Куда правка |
23
- | ---------------------------------------------- | -------------------------------------------------- |
24
- | какое правило гейт требует под какой файл | `.claude/rt-kit/gate-map.sh` — своя `skill_for` |
25
- | порты, адреса, линтеры, форма ветки, инвентарь | `.claude/rt-kit/project.sh` — свои `rt_*` |
26
- | пути и идентификаторы, которыми живут проверки | `.claude/rt-kit/checks.json` |
27
- | раздел разложенного текста | `.claude/rt-kit/overrides/<идентификатор ресурса>` |
28
- | что брать, а от чего отказаться | `.claude/rt-kit.json`, ключи `only` и `skip` |
29
- | сам механизм гард, проверка, текст правила | ресурс в пакете |
22
+ | Что меняешь | Куда правка |
23
+ | ---------------------------------------------- | ------------------------------------------------------ |
24
+ | какое правило гейт требует под какой файл | `.claude/rt-kit/gate-map.sh` — своя `skill_for` |
25
+ | порты, адреса, линтеры, форма ветки, инвентарь | `.claude/rt-kit/project.sh` — свои `rt_*` |
26
+ | пути и идентификаторы, которыми живут проверки | `.claude/rt-kit/checks.json` |
27
+ | какие признаки единообразия дерево берёт | `checks.json`, ключи `reuse.bundles` и `reuse.signals` |
28
+ | раздел разложенного текста | `.claude/rt-kit/overrides/<идентификатор ресурса>` |
29
+ | что брать, а от чего отказаться | `.claude/rt-kit.json`, ключи `only` и `skip` |
30
+ | сам механизм — гард, проверка, текст правила | ресурс в пакете |
30
31
 
31
32
  Надстройка объявляет функцию заново и вправе позвать умолчание тем же именем с суффиксом
32
33
  `_default`. Слияние текста идёт по разделам `## `: совпавший заголовок замещает, новый
33
34
  дописывается, пустой снимает раздел пакета.
34
35
 
36
+ Готовый пример на каждую строку этой таблицы — скил `agent-kit-extend`: как выглядит сама
37
+ правка, чем она проверяется и чем кончается, если положить её не туда.
38
+
35
39
  ## Порядок
36
40
 
37
41
  Правка ресурса доезжает до дерева только через сборку пакета: строка запуска читает собранное,
@@ -84,6 +88,10 @@ npx agent-kit propose # отправить предложения, адр
84
88
 
85
89
  ## Ловушки
86
90
 
91
+ - **Правила и паттерны при отвергнутом законе в отказе не перечисляются.** Их снимает каскад, а
92
+ строка на них становится выводимой: раскладка называет её лишней вместе с законом, из-за
93
+ которого она перестала снимать. Отказ мерит слой законов, а не число файлов в пакете.
94
+
87
95
  - **Линтер по следам правки судит файл целиком, а не внесённую правку.** Импорт, добавленный
88
96
  отдельным шагом, отбивается как неиспользуемый ещё до того, как появится строка, которая его
89
97
  зовёт, и работа встаёт на половине. Правка делается одним вызовом либо в порядке «сначала
@@ -103,10 +111,11 @@ npx agent-kit propose # отправить предложения, адр
103
111
  написанная по путям, требует под него доменное правило — а оно уводит править файл на месте.
104
112
  Правка на месте теряется на следующей раскладке, и до тех пор выглядит применённой. Ветка по
105
113
  шапке ставится в карте первой и решает раньше путей.
106
- - **Настройки проверок сливаются на один уровень.** Верхние ключи `checks.json` ложатся поверх
107
- умолчаний по одному, а вложенный объект замещается целиком: назвав один ключ борды, дерево
108
- теряет остальные и увидит это отказом «нет токена бота», то есть как неполадку машины.
109
- Вложенный раздел заполняется целиком либо не заводится вовсе.
114
+ - **Настройки проверок сливаются по ключам, а списки замещаются.** Объект `checks.json`
115
+ ложится поверх умолчания ключ за ключом на любой глубине: назвав один ключ борды, дерево не
116
+ теряет соседних. Список приходит целиком назвав корни исходников, дерево получает ровно
117
+ названное, а не умолчание вместе со своим: «дописать в список» и «убрать из списка» в этой
118
+ записи неразличимы.
110
119
  - **Утверждение правила переезжает вместе с кодом.** Вынесенное в надстройку перестаёт
111
120
  находиться по прежнему символу, и привязка в спутнике правила врёт молча — сверка спеков
112
121
  ловит это, но только если её позвать.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/agent-kit",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",
Binary file
Binary file