@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
package/README.md CHANGED
@@ -293,6 +293,18 @@ npx agent-kit propose --dry-run # что уехало бы
293
293
  npx agent-kit propose # отправить груз в приём
294
294
  ```
295
295
 
296
+ Чтобы грузу было чем представиться, у дерева должен быть токен. Выдаётся он один раз — по
297
+ приглашению, которое владелец приёма выписал этому дереву:
298
+
299
+ ```bash
300
+ npx agent-kit enroll --code <код приглашения> # завести дерево и положить его токен
301
+ ```
302
+
303
+ Код одноразовый и годен ограниченное время; имя дерева называет приглашение, а не обращение.
304
+ Токен ложится в файл, названный ключом `token`, правами «читает только владелец файла»; лежащий
305
+ токен команда молча не перезаписывает — для этого есть довод `--force`. Адрес приёма без TLS
306
+ команда отбивает у себя, не доходя до сети: токен уходит единственным ответом на обращение.
307
+
296
308
  Груз уезжает при каждом прогоне тремя родами: **сводка наблюдений со снимком надстроек**,
297
309
  **предложения** с адресом «пакет» — когда они есть — и **разборы происшествий**. Прогон без
298
310
  замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево
@@ -203,8 +203,55 @@ export function fetchIssue(number, options) {
203
203
  }
204
204
  }
205
205
 
206
+ /**
207
+ * Вершина берётся вместе с остальным: спросить её потом значило бы второй вызов на каждый PR,
208
+ * а судят по ней и папку задачи, и прогон.
209
+ */
206
210
  export function fetchOpenPulls(options) {
207
- return ghJson(['pr', 'list', '--state', 'open', '--limit', '200', '--json', 'number,title,headRefName,body'], options);
211
+ return ghJson(
212
+ ['pr', 'list', '--state', 'open', '--limit', '200', '--json', 'number,title,headRefName,headRefOid,isDraft,body'],
213
+ options
214
+ );
215
+ }
216
+
217
+ /**
218
+ * Сколько прогонов завелось на этой вершине.
219
+ *
220
+ * Спрашивается вершина, а не ветка: прогон промежуточного коммита о состоянии вершины не
221
+ * говорит ничего, а список прогонов ветки отдаёт их вперемешку.
222
+ */
223
+ export function runsOnHead(sha, options) {
224
+ const answer = gh(['api', `repos/${OWNER}/${REPO}/actions/runs?head_sha=${sha}&per_page=1`, '--jq', '.total_count'], options);
225
+ return Number(String(answer).trim());
226
+ }
227
+
228
+ /**
229
+ * Чем кончились прогоны на этой вершине: `success`, если все завершились успехом, `running`,
230
+ * если хоть один ещё идёт, `failure` — если хоть один упал. Прогонов нет вовсе — `none`.
231
+ *
232
+ * Цвет спрашивается отдельно от факта: факт отвечает на вопрос «событие дошло», цвет — на
233
+ * вопрос «работу можно отдавать». Второй вопрос задаётся там, где готовое стоит черновиком.
234
+ */
235
+ export function verdictOnHead(sha, options) {
236
+ const answer = gh(
237
+ [
238
+ 'api',
239
+ `repos/${OWNER}/${REPO}/actions/runs?head_sha=${sha}&per_page=20`,
240
+ '--jq',
241
+ '[.workflow_runs[] | {status, conclusion}] | if length == 0 then "none"' +
242
+ ' elif any(.status != "completed") then "running"' +
243
+ ' elif any(.conclusion != "success") then "failure"' +
244
+ ' else "success" end',
245
+ ],
246
+ options
247
+ );
248
+ return String(answer).trim();
249
+ }
250
+
251
+ /** Когда вершина легла в ветку — по времени коммита у хостинга, а не по местным часам ветки. */
252
+ export function headCommittedAt(sha, options) {
253
+ const answer = gh(['api', `repos/${OWNER}/${REPO}/commits/${sha}`, '--jq', '.commit.committer.date'], options);
254
+ return Date.parse(String(answer).trim());
208
255
  }
209
256
 
210
257
  /** `[<КЛЮЧ>-<номер>]` в начале заголовка — единственная форма номера в названиях */
@@ -19,12 +19,16 @@
19
19
  * Момент, когда задачу берут в работу, отсюда не виден вовсе — ветки на борде нет, —
20
20
  * и «In progress» здесь не требуется ни от кого.
21
21
  *
22
+ * Прогон на вершине спрашивает тоже она: страница PR без прогона выглядит так же, как
23
+ * страница с зелёным, — цвета у неё нет ни там, ни там, — и вершина, за которой прогон
24
+ * не встал, узнаётся только тем, что кто-то открыл список прогонов руками.
25
+ *
22
26
  * Нет сети или нет токена — код возврата ноль: проверка, падающая в самолёте,
23
27
  * перестаёт что-либо значить.
24
28
  *
25
29
  * Ненулевой код возврата и перечень расхождений.
26
30
  */
27
- import { statSync } from 'node:fs';
31
+ import { existsSync, statSync } from 'node:fs';
28
32
  import { join } from 'node:path';
29
33
 
30
34
  import {
@@ -39,9 +43,12 @@ import {
39
43
  fetchIssues,
40
44
  fetchOpenPulls,
41
45
  gh,
46
+ headCommittedAt,
42
47
  numberFromTaskDir,
43
48
  numberFromTitle,
49
+ runsOnHead,
44
50
  taskDirs,
51
+ verdictOnHead,
45
52
  } from './board.mjs';
46
53
  import { CONFIG, ROOT } from './rt-kit-checks.config.mjs';
47
54
 
@@ -49,6 +56,17 @@ const IN_REVIEW = STATUS_OPTIONS[IN_REVIEW_STATUS].name;
49
56
  const TASKS_DIR = join(ROOT, CONFIG.tasksDir);
50
57
  /** Возраст брошенного черновика, после которого он перестаёт выглядеть начатым сегодня. */
51
58
  const DRAFT_DAYS = 7;
59
+ /**
60
+ * Сколько времени вершине даётся на то, чтобы прогон за ней встал. Событие доходит до хостинга
61
+ * не мгновенно, и сверка, позванная сразу после пуша, иначе краснела бы на здоровой ветке.
62
+ */
63
+ const RUN_GRACE_MINUTES = 10;
64
+ /**
65
+ * Конвейер дерева. Прогоны спрашиваются только там, где ему есть откуда взяться: дерево без
66
+ * конвейера получило бы строку на каждый открытый PR, и не о чём.
67
+ */
68
+ const PIPELINE = CONFIG.pushGate?.pipelineFile ?? '';
69
+ const HAS_PIPELINE = PIPELINE !== '' && existsSync(join(ROOT, PIPELINE));
52
70
 
53
71
  const problems = [];
54
72
  const report = (message) => problems.push(message);
@@ -104,6 +122,62 @@ function folderInBranch(branch, options) {
104
122
  }
105
123
  }
106
124
 
125
+ /**
126
+ * Прогон на вершине открытого PR.
127
+ *
128
+ * Молчание страницы и зелёный прогон читаются одинаково, а событие до хостинга доходит не
129
+ * всегда: в час его отказов пуш прошёл, а прогона за ним не встало. Сверка называет такую
130
+ * вершину, пока PR ещё открыт, — после слияния об этом узнавать поздно.
131
+ *
132
+ * Считается сам факт прогона, а не его цвет. Идущий и упавший прогон видны на странице PR оба;
133
+ * невидимо только отсутствие, и говорит сверка ровно о нём.
134
+ *
135
+ * Свежая вершина не судится: между пушем и прогоном проходит время, и красная строка на этом
136
+ * промежутке значила бы «подожди», а не «чини».
137
+ */
138
+ function checkHeadRun(pull, options) {
139
+ if (runsOnHead(pull.headRefOid, options) > 0) {
140
+ checkReadyDraft(pull, options);
141
+ return;
142
+ }
143
+
144
+ const minutes = Math.floor((Date.now() - headCommittedAt(pull.headRefOid, options)) / 60000);
145
+ if (minutes < RUN_GRACE_MINUTES) {
146
+ return;
147
+ }
148
+
149
+ report(
150
+ `PR #${pull.number}: на вершине ${pull.headRefOid.slice(0, 8)} прогона нет, а лежит она ${minutes} мин — ` +
151
+ `конвейер события не получил; верни его новым коммитом либо перезакрытием PR ` +
152
+ `(gh pr close ${pull.number} && gh pr reopen ${pull.number})`
153
+ );
154
+ }
155
+
156
+ /**
157
+ * Готовая работа, оставленная черновиком.
158
+ *
159
+ * У черновика кнопка слияния заблокирована самим хостингом, поэтому зелёная страница PR
160
+ * владельцу ничего не разрешает: список, в котором всё серое, читается как «работа не сделана».
161
+ * Гард снятия черновика сюда не достаёт — он судит один ход и молчит, пока ветка везёт папку
162
+ * своей задачи; сверка же смотрит на состояние очереди целиком.
163
+ *
164
+ * Четыре PR так и простояли черновиками двое суток — разбор
165
+ * `docs/postmortems/2026-08-18-ready-work-left-in-drafts.md`.
166
+ */
167
+ function checkReadyDraft(pull, options) {
168
+ if (pull.isDraft !== true) {
169
+ return;
170
+ }
171
+ if (verdictOnHead(pull.headRefOid, options) !== 'success') {
172
+ return;
173
+ }
174
+
175
+ report(
176
+ `PR #${pull.number}: прогон на вершине ${pull.headRefOid.slice(0, 8)} зелёный, а PR черновик — ` +
177
+ `разбери папку задачи и сними черновик (gh pr ready ${pull.number}) либо скажи владельцу, чего ждёшь`
178
+ );
179
+ }
180
+
107
181
  let checked = { issues: 0, pulls: 0 };
108
182
 
109
183
  // Черновики судятся по диску и потому проверяются всегда: связи для этого не нужно.
@@ -160,6 +234,10 @@ try {
160
234
  // сверка обязана назвать ДО слияния: гард судит её на слиянии, а слияние нажимает
161
235
  // человек в браузере, где хуков нет вовсе. Сказанная после, эта строка уже не чинится
162
236
  // тем же PR — работа перешла дальше, и на разбор заводится вторая задача.
237
+ if (HAS_PIPELINE && pull.headRefOid) {
238
+ checkHeadRun(pull, options);
239
+ }
240
+
163
241
  if (!FOLDER_SKIP.test(String(pull.body ?? '')) && pull.headRefName) {
164
242
  const folder = folderInBranch(pull.headRefName, options);
165
243
  if (folder !== null) {
@@ -210,6 +288,11 @@ try {
210
288
  }
211
289
  }
212
290
 
291
+ // Непроверенное называется вслух: молчание о прогонах читалось бы как «прогоны на месте».
292
+ if (!offline && !HAS_PIPELINE) {
293
+ console.log('check-board: прогоны на вершинах не спрашивались — файла конвейера в дереве нет');
294
+ }
295
+
213
296
  if (problems.length > 0) {
214
297
  console.error(`check-board: расхождений ${problems.length}\n`);
215
298
  problems.forEach((problem) => console.error(` ${problem}`));