@rt-tools/agent-kit 0.8.1 → 0.8.3

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 (129) hide show
  1. package/README.md +25 -19
  2. package/assets/agents/qa-engineer.md +1 -1
  3. package/assets/agents/rules-reviewer.md +83 -0
  4. package/assets/checks/board.github.mjs +56 -17
  5. package/assets/checks/check-board.github.mjs +49 -5
  6. package/assets/checks/check-dupes.mjs +66 -6
  7. package/assets/checks/check-specs.mjs +100 -15
  8. package/assets/checks/rt-kit-checks.config.mjs +9 -0
  9. package/assets/checks/task-new.github.mjs +33 -5
  10. package/assets/commands/agent-kit-digest.md +10 -5
  11. package/assets/commands/feedback.md +95 -0
  12. package/assets/commands/next-session.md +4 -4
  13. package/assets/commands/rules-review.md +98 -0
  14. package/assets/commands/skill-curator.md +44 -25
  15. package/assets/defaults/gate-map.sh +11 -4
  16. package/assets/defaults/project.sh +46 -0
  17. package/assets/docs/GLOSSARY.md +49 -46
  18. package/assets/hooks/docs-guard.sh +19 -3
  19. package/assets/hooks/git-guard-delivery.sh +106 -13
  20. package/assets/hooks/proposal-guard.sh +93 -0
  21. package/assets/hooks/reuse-first-guard.sh +83 -15
  22. package/assets/hooks/skill-gate-layers.sh +1 -1
  23. package/assets/hooks/skill-gate.sh +27 -1
  24. package/assets/hooks/task-flow-guard.sh +59 -19
  25. package/assets/hooks/waiting-turn-guard.sh +87 -0
  26. package/assets/hooks/window-fill-guard.sh +1 -1
  27. package/assets/laws/delivery.md +41 -7
  28. package/assets/laws/project-documentation.md +39 -0
  29. package/assets/laws/verifiability.md +6 -1
  30. package/assets/laws/work-conduct.md +83 -3
  31. package/assets/patterns/git-workflow-commit.azure.md +84 -4
  32. package/assets/patterns/git-workflow-commit.github.md +90 -4
  33. package/assets/patterns/git-workflow-commit.gitlab.md +84 -5
  34. package/assets/patterns/git-workflow-docker.md +30 -0
  35. package/assets/patterns/git-workflow-merge.md +1 -1
  36. package/assets/patterns/spec-driven-domain.md +7 -1
  37. package/assets/patterns/spec-driven-rule.md +6 -0
  38. package/assets/patterns/task-flow-close.md +197 -21
  39. package/assets/patterns/task-flow-handoff.md +28 -5
  40. package/assets/patterns/task-flow-resume.md +25 -7
  41. package/assets/patterns/task-flow-start.md +37 -6
  42. package/assets/rules/angular-patterns.md +22 -0
  43. package/assets/rules/api-layer.md +25 -0
  44. package/assets/rules/browser-verification.md +42 -1
  45. package/assets/rules/component-structure.md +21 -0
  46. package/assets/rules/dependencies.md +22 -0
  47. package/assets/rules/doc-style.md +37 -5
  48. package/assets/rules/{entity-conventions.md → entity-conventions.needs-admin.md} +21 -0
  49. package/assets/rules/entity-models.md +21 -0
  50. package/assets/rules/git-workflow.azure.md +62 -8
  51. package/assets/rules/git-workflow.github.md +78 -14
  52. package/assets/rules/git-workflow.gitlab.md +61 -8
  53. package/assets/rules/lib-layers.md +25 -0
  54. package/assets/rules/lists.md +27 -0
  55. package/assets/rules/navigation.md +21 -0
  56. package/assets/rules/{observability.md → observability.needs-app.md} +23 -0
  57. package/assets/rules/permissions.md +23 -0
  58. package/assets/rules/platform-access.md +21 -0
  59. package/assets/rules/reuse-first.md +20 -0
  60. package/assets/rules/seo.md +19 -0
  61. package/assets/rules/shared-code.md +19 -0
  62. package/assets/rules/spec-driven.md +36 -0
  63. package/assets/rules/styling-bem.md +19 -0
  64. package/assets/rules/task-flow.md +150 -35
  65. package/assets/rules/testing.md +50 -0
  66. package/assets/rules/translations.md +21 -0
  67. package/assets/rules/typescript-conventions.md +33 -0
  68. package/assets/samples/specs/_template/spec.md +83 -0
  69. package/assets/samples/tasks/_template/grill.md +28 -0
  70. package/assets/samples/tasks/_template/plan.md +39 -0
  71. package/assets/samples/tasks/_template/progress.md +23 -0
  72. package/assets/skills/agent-kit-extend.md +24 -0
  73. package/assets/skills/agent-kit.md +69 -7
  74. package/assets/templates/rule.md +31 -2
  75. package/assets/traits.json +14 -0
  76. package/bin/agent-kit.d.ts.map +1 -1
  77. package/bin/agent-kit.js +31 -16
  78. package/bin/agent-kit.js.map +1 -1
  79. package/index.d.ts +1 -0
  80. package/index.d.ts.map +1 -1
  81. package/index.js +1 -0
  82. package/index.js.map +1 -1
  83. package/lib/argv.d.ts +17 -0
  84. package/lib/argv.d.ts.map +1 -0
  85. package/lib/argv.js +44 -0
  86. package/lib/argv.js.map +1 -0
  87. package/lib/cargo.d.ts +88 -0
  88. package/lib/cargo.d.ts.map +1 -0
  89. package/lib/cargo.js +16 -0
  90. package/lib/cargo.js.map +1 -0
  91. package/lib/catalog.d.ts +18 -1
  92. package/lib/catalog.d.ts.map +1 -1
  93. package/lib/catalog.js +12 -2
  94. package/lib/catalog.js.map +1 -1
  95. package/lib/commands.d.ts +0 -26
  96. package/lib/commands.d.ts.map +1 -1
  97. package/lib/commands.js +79 -122
  98. package/lib/commands.js.map +1 -1
  99. package/lib/companion.d.ts +37 -0
  100. package/lib/companion.d.ts.map +1 -1
  101. package/lib/companion.js +42 -1
  102. package/lib/companion.js.map +1 -1
  103. package/lib/config.d.ts +38 -1
  104. package/lib/config.d.ts.map +1 -1
  105. package/lib/config.js +24 -0
  106. package/lib/config.js.map +1 -1
  107. package/lib/ship.d.ts +39 -0
  108. package/lib/ship.d.ts.map +1 -0
  109. package/lib/ship.js +87 -0
  110. package/lib/ship.js.map +1 -0
  111. package/lib/shipment.d.ts +60 -0
  112. package/lib/shipment.d.ts.map +1 -0
  113. package/lib/shipment.js +247 -0
  114. package/lib/shipment.js.map +1 -0
  115. package/lib/snapshot.d.ts +30 -0
  116. package/lib/snapshot.d.ts.map +1 -0
  117. package/lib/snapshot.js +73 -0
  118. package/lib/snapshot.js.map +1 -0
  119. package/lib/traits.d.ts +32 -0
  120. package/lib/traits.d.ts.map +1 -0
  121. package/lib/traits.js +82 -0
  122. package/lib/traits.js.map +1 -0
  123. package/package.json +6 -2
  124. package/rt-tools-agent-kit-0.8.3.tgz +0 -0
  125. package/lib/submit.d.ts +0 -24
  126. package/lib/submit.d.ts.map +0 -1
  127. package/lib/submit.js +0 -26
  128. package/lib/submit.js.map +0 -1
  129. package/rt-tools-agent-kit-0.8.1.tgz +0 -0
@@ -93,8 +93,32 @@ const E2E_ROOTS = CONFIG.e2eRoots;
93
93
  * выглядит как правило без якоря. Заглавные и десять букв нужны ради `api.Dockerfile`:
94
94
  * без них правило про режим исполнения образа считалось правилом с пустой привязкой,
95
95
  * а привязать его больше не к чему — режим объявлен ровно там.
96
+ *
97
+ * Символ — любая буква, а не только латинская: тексты, которые исполняет модель, написаны
98
+ * своим языком, и латиницей в них называется ровно то, что утверждения не держит — имя поля
99
+ * шапки, имя инструмента. Привязанное к имени поля утверждение остаётся зелёным, когда текст
100
+ * переписан целиком. Алфавит не перечисляется диапазонами: перечисленные молча не покрывают
101
+ * соседнего, и промах выглядит отсутствием привязки. Путь при этом остаётся латинским — он
102
+ * адрес в дереве, а не слово текста.
103
+ */
104
+ const ANCHOR = /`([\w./-]+\.[A-Za-z]{2,10}):(\p{L}[\p{L}\p{N}_-]*|_[\w-]*)`/gu;
105
+ /**
106
+ * Явный вердикт вместо якоря: статья, которой в дереве исполняться негде. Так бывает
107
+ * законно — правило говорит о службе, которой дерево не держит, или о движении человека,
108
+ * до которого проверке не дотянуться: кнопку слияния нажимают в браузере, где хуков нет
109
+ * вовсе. Якорь такой статье можно поставить только в файл, который её не исполняет, —
110
+ * проверка примет, а читателю совратёт.
111
+ *
112
+ * Принимается вердикт с причиной, а не одно слово: пустой он становится способом закрыть
113
+ * любую строку, и таблица за месяц превращается в список отговорок. Порог длины — та же
114
+ * мера, что у обхода гарда документов: причина короче его причиной не считается.
115
+ *
116
+ * Конец слова ищется отрицательным просмотром, а не `\b`: границей слова JavaScript знает
117
+ * только латиницу, и после кириллической буквы её нет вовсе — вердикт не опознавался ни
118
+ * разу.
96
119
  */
97
- const ANCHOR = /`([\w./-]+\.[A-Za-z]{2,10}):([A-Za-z_][\w-]*)`/g;
120
+ const VERDICT = /^\s*(?:\*\*)?Не (?:исполняется|применимо|проверяется)(?![\p{L}\p{N}_])/u;
121
+ const VERDICT_MIN = 40;
98
122
  /** Строка шапки, объявляющая либы, чьи процедуры домен обслуживает */
99
123
  const PROCEDURE_ROOTS = /^\*\*Процедуры:\*\*\s*(.+)$/;
100
124
  const BACKTICKED = /`([^`]+)`/g;
@@ -204,9 +228,19 @@ function bulletsOf(lines) {
204
228
 
205
229
  const escapeForRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&');
206
230
 
207
- /** Символ ищется как слово: подстрока дала бы ложное совпадение на префиксе. */
231
+ /**
232
+ * Символ ищется как слово: подстрока дала бы ложное совпадение на префиксе.
233
+ *
234
+ * Границы слова считаются буквой любого алфавита, а не `\b`: он в JavaScript знает буквой
235
+ * только латиницу, и `\bСемья\b` не совпадает ни разу — привязка на русском слове читалась
236
+ * как ведущая в файл, где этого слова нет, при том что слово стоит там первой же строкой.
237
+ */
208
238
  function fileHasSymbol(path, symbol) {
209
- return new RegExp(`\\b${escapeForRegExp(symbol)}\\b`).test(read(path));
239
+ // Дефис здесь не экранируется: вне класса символов он ничего не значит, а под флагом `u`
240
+ // лишнее экранирование — уже отказ разбора. Общий экранировщик его защищает, потому что
241
+ // рассчитан и на класс тоже, и `task-flow` роняло всю сверку целиком.
242
+ const word = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
243
+ return new RegExp(`(?<![\\p{L}\\p{N}_])${word}(?![\\p{L}\\p{N}_])`, 'u').test(read(path));
210
244
  }
211
245
 
212
246
  /**
@@ -287,7 +321,12 @@ function checkRuleImplementation(specFile, text, mapFile, heading = '## Прав
287
321
  if (!head || head === 'Правило' || head === 'Статья' || /^-+$/.test(head)) {
288
322
  continue;
289
323
  }
290
- rows.set(head, { anchors: [...cells[2].matchAll(ANCHOR)], used: false });
324
+ const cell = cells[2].trim();
325
+ rows.set(head, {
326
+ anchors: [...cells[2].matchAll(ANCHOR)],
327
+ verdict: VERDICT.test(cell) && cell.length >= VERDICT_MIN,
328
+ used: false,
329
+ });
291
330
  }
292
331
 
293
332
  for (const bullet of bullets) {
@@ -301,13 +340,17 @@ function checkRuleImplementation(specFile, text, mapFile, heading = '## Прав
301
340
  report(
302
341
  mapFile,
303
342
  `правило без привязки: «${head.slice(0, 60)}…» — допиши строку с \`файл:символ\`, ` +
304
- 'либо перенеси правило в «Открытые вопросы» как Q-N'
343
+ 'вердиктом «Не исполняется» с причиной либо перенеси правило в «Открытые вопросы» как Q-N'
305
344
  );
306
345
  continue;
307
346
  }
308
347
  row.used = true;
309
- if (!row.anchors.length) {
310
- report(mapFile, `у правила «${head.slice(0, 60)}…» пустая привязка`);
348
+ if (!row.anchors.length && !row.verdict) {
349
+ report(
350
+ mapFile,
351
+ `у правила «${head.slice(0, 60)}…» пустая привязка — поставь \`файл:символ\` ` +
352
+ 'либо вердикт «Не исполняется», «Не применимо», «Не проверяется» с причиной'
353
+ );
311
354
  }
312
355
  for (const [, path, symbol] of row.anchors) {
313
356
  if (!exists(path)) {
@@ -883,9 +926,11 @@ for (const domain of domains) {
883
926
 
884
927
  const found = walk(base, (name) => name === 'scenarios.md').flatMap(parseScenarios);
885
928
 
886
- // Префикс судится в пределах одного спека, а не всего дерева домена: у поддомена он свой, и
887
- // по номеру видно, о чём сценарий. Два префикса в одном спеке по-прежнему означают, что
888
- // предмет описан дважды.
929
+ // Префикс принадлежит домену вместе с его поддоменами, а не отдельному каталогу. Домен
930
+ // делится тогда, когда его спек перерос предел длины, и сценарии переезжают в поддомены
931
+ // прежними: номер — единственное, чем сценарий связан с заголовком теста, и своя нумерация
932
+ // у каждого поддомена означала бы пересчёт всех номеров разом. Два префикса в одном спеке
933
+ // по-прежнему означают, что предмет описан дважды.
889
934
  const prefixesOf = new Map();
890
935
  for (const scenario of found) {
891
936
  const dir = dirname(scenario.file);
@@ -906,11 +951,11 @@ for (const domain of domains) {
906
951
  }
907
952
  for (const prefix of prefixes) {
908
953
  const owner = prefixOwners.get(prefix);
909
- if (owner && owner !== dir) {
954
+ if (owner && owner !== base) {
910
955
  report(dir, `префикс \`SC-${prefix}\` уже занят — \`${owner}\`; по номеру не видно, чей сценарий`);
911
956
  continue;
912
957
  }
913
- prefixOwners.set(prefix, dir);
958
+ prefixOwners.set(prefix, base);
914
959
  }
915
960
  }
916
961
 
@@ -1008,12 +1053,52 @@ const isProposedLaw = (file) => /^\*\*Статус:\*\*\s*предложен/m.t
1008
1053
  .filter(([law, file]) => !ruled.has(law) && !isProposedLaw(file))
1009
1054
  .forEach(([, file]) => report(file, 'у закона нет ни одного правила — заведи скил с `law:` на него'));
1010
1055
 
1056
+ /**
1057
+ * Имена паттернов, которые дерево при раскладке пропустило: ключ `skip` в настройке проекта.
1058
+ *
1059
+ * Пропуск — выбор дерева, а не забытая работа: правило о процедурах бэкенда ложится и в дерево,
1060
+ * где бэкенда нет вовсе. Требовать там паттерн значит требовать завести файл, которому нечего
1061
+ * сказать, — и единственным способом позеленеть становится снятие пропуска.
1062
+ */
1063
+ const skippedPatterns = () => {
1064
+ const path = '.claude/rt-kit.json';
1065
+ if (!exists(path)) {
1066
+ return new Set();
1067
+ }
1068
+ try {
1069
+ const skip = JSON.parse(read(path)).skip ?? [];
1070
+
1071
+ return new Set(skip.map((resource) => resource.match(/^patterns\/(.+)\.md$/)?.[1]).filter(Boolean));
1072
+ } catch {
1073
+ return new Set();
1074
+ }
1075
+ };
1076
+
1077
+ /**
1078
+ * Раздел «Паттерны» самого правила — единственное место, где связь видна без файла паттерна:
1079
+ * пропущенного файла в дереве нет, и поле `rule:` в нём спросить не у кого.
1080
+ */
1081
+ // Флага `m` здесь нет намеренно: с ним `$` означает конец строки, и раздел кончается на первом
1082
+ // же переводе строки — пустым. Начало заголовка поэтому ищется своей парой, а не якорем.
1083
+ const PATTERNS_HEADING = /(?:^|\n)## Паттерны\n([\s\S]*?)(?=\n## |$)/;
1084
+ const patternsNamedBy = (text) => [...(text.match(PATTERNS_HEADING)?.[1] ?? '').matchAll(/^-\s+`([\w-]+)`/gm)].map(([, found]) => found);
1085
+
1086
+ const skipped = skippedPatterns();
1087
+
1011
1088
  for (const file of walk('.claude/skills', (name) => name === 'SKILL.md')) {
1012
- const head = frontMatterOf(read(file));
1089
+ const text = read(file);
1090
+ const head = frontMatterOf(text);
1013
1091
  const name = nameOf(head);
1014
- if (/^kind:\s*rule\s*$/m.test(head) && name && !patterned.has(name)) {
1015
- report(file, 'у правила нет ни одного паттерна — заведи скил с `rule:` на него');
1092
+ if (!/^kind:\s*rule\s*$/m.test(head) || !name || patterned.has(name)) {
1093
+ continue;
1016
1094
  }
1095
+
1096
+ const named = patternsNamedBy(text);
1097
+ if (named.length > 0 && named.every((pattern) => skipped.has(pattern))) {
1098
+ continue;
1099
+ }
1100
+
1101
+ report(file, 'у правила нет ни одного паттерна — заведи скил с `rule:` на него');
1017
1102
  }
1018
1103
 
1019
1104
  checkTracedAnchors();
@@ -71,6 +71,15 @@ const DEFAULTS = {
71
71
  * числе на каждой правке, а не о длине файла.
72
72
  */
73
73
  fileSizeLimit: 500,
74
+ /**
75
+ * Внешние пакеты, чьи перечисления считаются наравне с либами: своё перечисление под уже
76
+ * объявленный там набор — такая же копия, как и между двумя либами. Каждая запись — имя
77
+ * пакета и каталог объявлений внутри него; каталог ищется разрешением модуля, а не путём в
78
+ * `node_modules`: пакет, объявленный зависимостью подпроекта, в корне дерева не лежит вовсе,
79
+ * и зашитый путь на такой раскладке верным не бывает никогда. Пусто — внешние наборы не
80
+ * считаются.
81
+ */
82
+ externalEnums: [],
74
83
  /** Корни сквозных тестов; пусто — их в дереве нет. */
75
84
  e2eRoots: ['apps/site-e2e', 'apps/admin-e2e'],
76
85
  /** Корни бэкенда: у него нет ни компонентов, ни шаблонов, и часть признаков к нему не применяется. */
@@ -31,12 +31,13 @@ import {
31
31
  REPO,
32
32
  STATUS_FIELD_ID,
33
33
  TASK_KEY,
34
- TOKEN_PATH,
35
34
  botToken,
35
+ describeTaskState,
36
36
  gh,
37
37
  ghJson,
38
38
  graphql,
39
39
  numberFromTitle,
40
+ taskState,
40
41
  } from './board.mjs';
41
42
 
42
43
  function parseArgs(argv) {
@@ -94,10 +95,13 @@ if (args.slug !== null && !/^[a-z0-9][a-z0-9-]*$/.test(args.slug)) {
94
95
  fail('slug — строчные латинские буквы, цифры и дефисы: имя ветки читают в списке из полусотни строк');
95
96
  }
96
97
 
97
- const token = botToken();
98
- if (!token) {
99
- fail(`нет токена бота (${TOKEN_PATH}): задача завелась бы от чужого имени`);
100
- }
98
+ /**
99
+ * Токен машинной записи необязателен: не назвавшее его дерево заводит задачу учётной записью,
100
+ * под которой залогинен клиент хостинга. Требование токена держало бы заведение задач у дерева,
101
+ * машинной записи не заводившего, и у дерева, чью запись ограничил хостинг, — а без заведения
102
+ * не начинается никакая работа вовсе. Кто именно завёл задачу, читается у неё самой.
103
+ */
104
+ const token = botToken() ?? undefined;
101
105
 
102
106
  let number = null;
103
107
  try {
@@ -194,5 +198,29 @@ adoptDraft();
194
198
 
195
199
  console.log(`[${TASK_KEY}-${number}] ${args.title}`);
196
200
  console.log(`https://github.com/${OWNER}/${REPO}/issues/${number}`);
201
+
202
+ /**
203
+ * Пятый шаг: заведение подтверждается ответом очереди работ, а не выводом этой команды.
204
+ *
205
+ * Все четыре шага выше отвечают за свои вызовы и молчат о том, видна ли задача тому, кто по
206
+ * ней придёт. Шестнадцать заведений подряд так и напечатали номер со ссылкой, не попав в
207
+ * очередь ни одно: учётная запись была ограничена хостингом, вызовы при этом отказа не дали.
208
+ */
209
+ let answer;
210
+ try {
211
+ answer = describeTaskState(number, taskState(number, { token }));
212
+ } catch (error) {
213
+ const reason = error instanceof OfflineError ? error.message : String(error.message ?? error);
214
+ answer = describeTaskState(number, { offline: reason });
215
+ }
216
+ for (const line of answer.lines) {
217
+ (answer.ok ? console.log : console.error)(`task-new: ${line}`);
218
+ }
219
+
197
220
  console.log(`\nВетка заводится отдельным вызовом:\n git checkout -b ${branch}`);
198
221
  console.log(`Взятая в работу задача переставляется на борде:\n npm run task:move -- ${number} ${IN_PROGRESS_STATUS}`);
222
+
223
+ if (!answer.ok) {
224
+ console.error(`task-new: проверь очередь работ целиком — npm run check:board`);
225
+ process.exit(1);
226
+ }
@@ -12,17 +12,22 @@ argument-hint: '[пусто | --days N]'
12
12
 
13
13
  ## 1. Собери, что пришло
14
14
 
15
+ Груз уезжает в приём, а не в очередь работ: сводка говорит о рабочих привычках команды, и в
16
+ открытой очереди это выложено всему свету. Записи приёма читает его админка; пока её нет,
17
+ собранное читается запросом к его хранилищу — как именно, сказано в компаньоне этой команды.
18
+
19
+ Записи, заведённые прежним порядком, лежат в очереди работ и никуда не делись:
20
+
15
21
  ```bash
16
- gh issue list --label agent-kit-feedback --state open --limit 100 \
22
+ gh issue list --label agent-kit-feedback --state all --limit 100 \
17
23
  --json number,title,body,createdAt
18
24
  ```
19
25
 
20
26
  Помощник хостинга и учётная запись машинной работы у каждого дерева свои — как их звать здесь,
21
27
  сказано в компаньоне правила `git-workflow`.
22
28
 
23
- Каждая запись заведена командой `agent-kit propose` из дерева, где пакет стоит. В теле ресурс,
24
- место, повод, готовый текст и сводка наблюдений того дерева. Имени дерева там нет намеренно:
25
- различать их можно только по сводке и по времени.
29
+ В каждой записи ресурс, место, повод и готовый текст. Признак дерева, приехавший с грузом,
30
+ адреса дерева не выдаёт: он считается хешем и по нему различают деревья, а не находят их.
26
31
 
27
32
  Своё дерево тоже потребитель — его наблюдения читаются прямо:
28
33
 
@@ -79,5 +84,5 @@ gh issue close <номер> --comment 'Вошло в #<номер задачи>.
79
84
  одна фраза о том, что менялось и почему. Журнал изменений при выпуске собирается из заголовков,
80
85
  и переписывать их задним числом — работа заново.
81
86
 
82
- **Выпуск отсюда не запускается.** Это отдельное решение владельца: слияние отчёта пакета не
87
+ **Выпуск отсюда не запускается.** Это отдельное решение владельца: слияние PR пакета не
83
88
  публикует. Скажи, что накопилось на выпуск, и остановись.
@@ -0,0 +1,95 @@
1
+ ---
2
+ description: Слово о слое правил, сказанное посреди работы, ложится блоком в файл предложений
3
+ argument-hint: '<что мешает, чего не хватило, что сработало не так>'
4
+ ---
5
+
6
+ Положи слово пользователя блоком в файл предложений. Слово: `$ARGUMENTS`
7
+
8
+ Зовётся **посреди работы**, а не после неё: то, обо что споткнулись час назад, к разбору закрытой
9
+ задачи уже забыто, а сама реплика живёт до конца сессии и умирает вместе с ней. Разбор закрытой
10
+ задачи смотрит на загруженное и на ход работы; реплик он не видит вовсе.
11
+
12
+ Команда ничего не отправляет. Она кладёт блок на диск, а увозит его обычная отправка, позванная
13
+ отдельно. Скажи об этом пользователю последней строкой — иначе положенное читается как
14
+ отправленное, и он ждёт ответа, которого никто не посылал.
15
+
16
+ ## 1. Пойми, о чём слово
17
+
18
+ Слово пользователя — проза: «вот это правило мешает», «гейт требует не то», «этого в правилах
19
+ нет вовсе». Твоё дело — перевести её в три вещи:
20
+
21
+ - **адрес** — куда правка идёт;
22
+ - **ресурс** — что именно правится;
23
+ - **готовый текст** — ровно то, что вставить.
24
+
25
+ Адрес один из трёх, и выбирается он не по удобству:
26
+
27
+ пакет — правка ресурса @rt-tools/agent-kit; верна любому дереву и уезжает наружу
28
+ компаньон — implementation.md рядом с правилом: имена этого дерева и привязка статей
29
+ дерево — надстройка этого дерева; наружу не уезжает никогда
30
+
31
+ Ресурс называется идентификатором пакета — `rules/styling-bem.md`, `hooks/skill-gate.sh`,
32
+ `patterns/git-workflow-commit.md`, — а у адресов «компаньон» и «дерево» путём в дереве.
33
+
34
+ **Непонятный адрес спрашивается, а не назначается по догадке.** Неверный адрес уводит правку в
35
+ чужой репозиторий: сказанное о своём дереве уезжает всем, а сказанное обо всех остаётся лежать
36
+ дома. Пока пользователь не ответил, в файл не записывается ничего.
37
+
38
+ Спрашивать не надо, когда адрес виден из самого слова: речь о правиле, которое ты только что
39
+ грузил, — это `пакет`; речь об именах, путях и командах этого дерева — `компаньон` или `дерево`.
40
+
41
+ ## 2. Найди файл сегодняшнего дня
42
+
43
+ Блок ложится туда же, куда его кладёт разбор закрытой задачи: у них один адресат и один формат, а
44
+ второй файл рядом означал бы, что отправка читает два места, а пользователь не помнит, в каком
45
+ лежит его слово.
46
+
47
+ ```bash
48
+ ls .claude/rt-kit/proposals/$(date +%F)-*.md 2>/dev/null
49
+ ```
50
+
51
+ Нашёлся — дописывай в него. Не нашёлся — заведи с образца, назвав по ветке:
52
+
53
+ ```bash
54
+ mkdir -p .claude/rt-kit/proposals
55
+ cp .claude/rt-kit/templates/proposal.md \
56
+ ".claude/rt-kit/proposals/$(date +%F)-$(git branch --show-current).md"
57
+ ```
58
+
59
+ У свежего файла шапка образца остаётся, а незаполненный образец блока — `rules/<правило>.md` со
60
+ скобками — заменяется твоим блоком: отправка такой образец пропускает, но лежит он молчаливым
61
+ мусором.
62
+
63
+ ## 3. Напиши блок
64
+
65
+ Форма заголовка — не украшение: по ней отправка отбирает то, что уезжает наружу. Блок без адреса
66
+ в заголовке не уедет никуда и останется лежать молча.
67
+
68
+ ```markdown
69
+ ## <адрес> · <ресурс>
70
+
71
+ - **место:** раздел «<заголовок>», в конец
72
+ - **повод:** что в этой работе пошло не так без этого правила
73
+
74
+ > Готовый текст правки — ровно то, что вставить, в стиле соседних правил: по-русски,
75
+ > утверждением, без воды.
76
+ ```
77
+
78
+ Повод пишется от случая, а не от желания: «здесь было неудобно» правилом не становится. Слово
79
+ пользователя пересказывается его смыслом, а не твоими выводами о том, как надо было бы.
80
+
81
+ **Адреса этого дерева в тексте блока не бывает** — ни пути, ни имени корня, ни имени чужого
82
+ репозитория: файл уезжает в чужой репозиторий целиком. Найденный адрес отбивает отправку с
83
+ номером строки, и это проверка, а не напоминание. Правь текст, а не обходи её.
84
+
85
+ ## 4. Скажи, что вышло
86
+
87
+ Одной строкой: в какой файл лёг блок, сколько блоков в нём теперь и чем он уедет.
88
+
89
+ ```bash
90
+ npx agent-kit propose --dry-run # что уехало бы
91
+ npx agent-kit propose # отправить груз в приём
92
+ ```
93
+
94
+ Отправка увозит блоки с адресом «пакет» и метит их отправленными; блоки «компаньон» и «дерево»
95
+ остаются лежать — их правит тот, кто работает в этом дереве.
@@ -6,7 +6,7 @@ argument-hint: '[пусто | <что дописать в передачу от
6
6
  Закрой заход: приведи дерево к главной ветке, убери влитые ветки и напиши передачу для
7
7
  следующего захода. Дописка владельца к передаче: `$ARGUMENTS`
8
8
 
9
- Вызывается **последним действием захода** — после того, как работа закоммичена, а отчёт открыт
9
+ Вызывается **последним действием захода** — после того, как работа закоммичена, а PR открыт
10
10
  или влит. Команда ничего не мержит, не пушит и не открывает: закрытие захода — уборка, а не
11
11
  поставка.
12
12
 
@@ -45,7 +45,7 @@ branch="$(git branch --show-current)"
45
45
  ```
46
46
 
47
47
  Работа идёт **по правилу**, если имя ветки несёт номер задачи — это `rt_task_branch_ok` из
48
- профиля — или если в каталоге папок задач лежит папка с именем ветки. Отчёт **влит**, когда
48
+ профиля — или если в каталоге папок задач лежит папка с именем ветки. PR **влит**, когда
49
49
  коммиты ветки уже есть в удалённой главной:
50
50
 
51
51
  ```bash
@@ -54,7 +54,7 @@ git merge-base --is-ancestor HEAD "origin/${RT_MAIN_BRANCH:-main}" && echo вл
54
54
 
55
55
  ## 4. Приведи дерево к главной ветке
56
56
 
57
- - **Работа по правилу и отчёт влит** — задача закрыта, ветка больше не нужна:
57
+ - **Работа по правилу и PR влит** — задача закрыта, ветка больше не нужна:
58
58
 
59
59
  ```bash
60
60
  git switch "${RT_MAIN_BRANCH:-main}" && git pull --ff-only
@@ -116,7 +116,7 @@ mkdir -p "${RT_HANDOFF_DIR:-.claude/handoff}"
116
116
 
117
117
  ## Чего команда не делает
118
118
 
119
- - не мержит отчёт и не пушит: это поставка, и вслепую она не делается;
119
+ - не мержит PR и не пушит: это поставка, и вслепую она не делается;
120
120
  - не сносит невлитую ветку и не трогает папку задачи;
121
121
  - не коммитит передачу — она лежит вне дерева намеренно, иначе рядом с ходом работы заводится
122
122
  вторая запись об одном и том же.
@@ -0,0 +1,98 @@
1
+ ---
2
+ description: Смысловое ревью семьи текстов слоя правил — закон, его правила и паттерны при них
3
+ argument-hint: '<имя закона>'
4
+ ---
5
+
6
+ Прогони смысловое ревью одной семьи текстов пакета правил. Семья: `$ARGUMENTS`
7
+
8
+ Зовётся **в репозитории самого пакета**, а не в дереве, где он стоит: судятся исходные тексты
9
+ ресурсов, и лежат они только здесь. В чужом дереве лежит разложенная копия выбранных ресурсов, а
10
+ не семья целиком, и путей, по которым команда ходит, в нём нет вовсе: обход по ним вернёт
11
+ пустоту, неотличимую от «такого закона не бывает». Каталог ресурсов ниже назван так, как он
12
+ зовётся в репозитории пакета.
13
+
14
+ Считаемое ловит проверка полноты текстов — недостающий раздел, правило без паттерна, имя соседа,
15
+ которому в наборе ничего не отвечает. Здесь ищется то, чего она не считает: два текста,
16
+ говорящих об одном разное, и случай, которого не назвал ни один. Ответ роли не повторяется от
17
+ запуска к запуску, поэтому в гейт он не идёт и ветку не отбивает.
18
+
19
+ ## 1. Пойми, какая семья
20
+
21
+ Семья зовётся именем закона — без пути и без расширения: `work-conduct`, `delivery`,
22
+ `project-documentation`.
23
+
24
+ ```bash
25
+ ls projects/agent-kit/assets/laws/*.md projects/agent-kit/assets/laws/*/*.md
26
+ ```
27
+
28
+ **Имени нет** — назови человеку перечень имён и остановись. Догадываться по похожести нельзя:
29
+ ревью уйдёт на чужую семью, и его находки человек примет за находки о своей.
30
+
31
+ **Названо несколько** — гони по одной, по очереди, и ответы не смешивай: находка семьи читается
32
+ вместе с её законом, а сваленные в кучу они теряют адрес.
33
+
34
+ **Довода нет вовсе** — потребуй имя и напечатай перечень. Умолчания здесь нет: «первый
35
+ попавшийся закон» даёт прогон, неотличимый от осмысленного.
36
+
37
+ **Закон лежит в слое приложения** — это законный случай, а не промах: у денег, локалей и доступа
38
+ семья такая же. Читается он оттуда же, где лежит.
39
+
40
+ ## 2. Собери семью
41
+
42
+ Правило принадлежит закону полем `law:`, паттерн правилу — полем `rule:`. Приставка имени
43
+ ненадёжна: её несут не все паттерны.
44
+
45
+ ```bash
46
+ LAW=<имя закона>
47
+ grep -l "^law: $LAW\$" projects/agent-kit/assets/rules/*.md
48
+ ```
49
+
50
+ Дальше по каждому найденному правилу — его паттерны. Имя правила для поиска берётся голым: без
51
+ вида и без требования — оба стоят суффиксами в имени файла, а поле `rule:` у паттерна несёт
52
+ только само имя.
53
+
54
+ ```bash
55
+ RULE=<имя правила: первое слово имени файла, до первой точки>
56
+ grep -l "^rule: $RULE\$" projects/agent-kit/assets/patterns/*.md
57
+ ```
58
+
59
+ **Виды берутся все.** `git-workflow.github`, `git-workflow.gitlab`, `git-workflow.azure` — три
60
+ редакции одного правила: дерево раскладывает одну, а расходятся они молча.
61
+
62
+ **Суффикс требования — не вид.** `entity-conventions.needs-admin`, `observability.needs-app` —
63
+ это правила, которые дерево берёт, только когда объявило нужную черту. Имя с этим суффиксом в
64
+ поле `rule:` не стоит ни у одного паттерна: поиск по нему возвращает пустоту, и семья уезжает в
65
+ ревью без паттернов вовсе — молча, потому что пустой ответ выглядит как «паттернов нет».
66
+
67
+ **У закона нет ни одного правила** — скажи это человеку и остановись. Читать один закон нечем:
68
+ расхождение живёт между двумя текстами, а пробел уровня статьи без правил под ней — не находка
69
+ ревью, а отсутствие целого слоя. Сам по себе такой закон стоит разговора: его находит и проверка
70
+ полноты текстов, и она же скажет, сколько их.
71
+
72
+ ## 3. Запусти роль
73
+
74
+ Инструментом `Agent`, `subagent_type: 'rules-reviewer'`. В промпт — имя закона и полный список
75
+ путей: закон, все его правила со всеми видами, все паттерны при них. Список собираешь ты: роль
76
+ git-команд не зовёт и историю не читает.
77
+
78
+ Одна роль на семью. Веер из нескольких ролей со сведением ответов здесь не заводится: взгляд на
79
+ предмет один — два текста об одном говорят разное, — а сведение превращает дословные цитаты в
80
+ пересказ.
81
+
82
+ ## 4. Покажи находки человеку
83
+
84
+ **Как есть.** Роль возвращает цитаты дословно, и пересказ их портит: по пересказу человек не
85
+ отличит настоящее расхождение от прочтения роли.
86
+
87
+ Порядок сохрани: сперва расхождения — у каждого два места и две цитаты, — потом пробелы, у
88
+ которых второго места нет.
89
+
90
+ По каждой находке скажи своё: согласен или нет и почему. Правку не вноси — тексты правил
91
+ действуют на все будущие сессии всех деревьев, и решает по ним человек.
92
+
93
+ Находка, с которой человек согласился, идёт дальше двумя путями, и выбирает он:
94
+
95
+ - **правится тут же** — если это правка текста пакета и она укладывается в текущую работу;
96
+ - **уходит задачей** в очередь работ — если тянет за собой код, раскладку или другой закон.
97
+
98
+ Пустой ответ роли — тоже результат: скажи, что находок нет, и не выдумывай их из вежливости.