@rt-tools/agent-kit 0.8.3 → 0.9.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.
- package/README.md +21 -0
- package/assets/checks/board.github.mjs +48 -1
- package/assets/checks/check-board.github.mjs +84 -1
- package/assets/checks/check-lib-layers.mjs +13 -524
- package/assets/checks/check-specs.mjs +11 -782
- package/assets/checks/check-styles.mjs +185 -15
- package/assets/checks/lib-boundaries.mjs +143 -0
- package/assets/checks/lib-common.mjs +149 -0
- package/assets/checks/lib-domains.mjs +205 -0
- package/assets/checks/lib-manifests.mjs +60 -0
- package/assets/checks/lib-reexports.mjs +101 -0
- package/assets/checks/rt-kit-checks.config.mjs +17 -0
- package/assets/checks/spec-anchors.mjs +297 -0
- package/assets/checks/spec-common.mjs +222 -0
- package/assets/checks/spec-contract.mjs +152 -0
- package/assets/checks/spec-scenarios.mjs +201 -0
- package/assets/defaults/project.sh +8 -0
- package/assets/hooks/git-guard-push-tests.sh +8 -4
- package/assets/hooks/skill-gate.sh +1 -1
- package/assets/hooks/sql-guard-parse.sh +187 -0
- package/assets/hooks/sql-guard-request.sh +117 -0
- package/assets/hooks/sql-guard-target.sh +134 -0
- package/assets/hooks/sql-guard-write.sh +212 -0
- package/assets/hooks/sql-guard.sh +26 -596
- package/assets/hooks/waiting-turn-guard.sh +42 -13
- package/assets/laws/delivery.md +7 -0
- package/assets/laws/work-conduct.md +9 -0
- package/assets/patterns/admin-lists-screen.md +25 -14
- package/assets/patterns/admin-nav-item.md +1 -1
- package/assets/patterns/component-structure-new.md +1 -1
- package/assets/patterns/entity-aside.md +4 -2
- package/assets/patterns/observability-record.md +9 -0
- package/assets/patterns/shared-code-new.md +2 -2
- package/assets/patterns/task-flow-close.md +7 -1
- package/assets/rules/git-workflow.azure.md +7 -0
- package/assets/rules/git-workflow.github.md +41 -0
- package/assets/rules/git-workflow.gitlab.md +7 -0
- package/assets/rules/lib-layers.md +4 -0
- package/assets/rules/lists.md +10 -10
- package/assets/rules/shared-code.md +1 -1
- package/assets/rules/task-flow.md +47 -7
- package/assets/rules/testing.md +31 -0
- package/assets/rules/typescript-conventions.md +7 -0
- package/assets/skills/agent-kit.md +36 -0
- package/assets/templates/proposal.md +21 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +117 -87
- package/bin/agent-kit.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/argv.d.ts.map +1 -1
- package/lib/argv.js +6 -4
- package/lib/argv.js.map +1 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +2 -1
- package/lib/assets.js.map +1 -1
- package/lib/cargo.d.ts +20 -0
- package/lib/cargo.d.ts.map +1 -1
- package/lib/cargo.js.map +1 -1
- package/lib/cascade.d.ts +55 -0
- package/lib/cascade.d.ts.map +1 -0
- package/lib/cascade.js +131 -0
- package/lib/cascade.js.map +1 -0
- package/lib/catalog.d.ts +0 -75
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +44 -127
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +152 -85
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +5 -5
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +2 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +7 -5
- package/lib/config.js.map +1 -1
- package/lib/enroll.d.ts +64 -0
- package/lib/enroll.d.ts.map +1 -0
- package/lib/enroll.js +170 -0
- package/lib/enroll.js.map +1 -0
- package/lib/freshness.d.ts.map +1 -1
- package/lib/freshness.js +31 -17
- package/lib/freshness.js.map +1 -1
- package/lib/hooks-map.d.ts +30 -0
- package/lib/hooks-map.d.ts.map +1 -1
- package/lib/hooks-map.js +80 -18
- package/lib/hooks-map.js.map +1 -1
- package/lib/integrity.d.ts +1 -2
- package/lib/integrity.d.ts.map +1 -1
- package/lib/integrity.js +0 -1
- package/lib/integrity.js.map +1 -1
- package/lib/observations.d.ts.map +1 -1
- package/lib/observations.js +25 -12
- package/lib/observations.js.map +1 -1
- package/lib/order.d.ts +10 -0
- package/lib/order.d.ts.map +1 -0
- package/lib/order.js +14 -0
- package/lib/order.js.map +1 -0
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +8 -2
- package/lib/picker.js.map +1 -1
- package/lib/plan.js +1 -1
- package/lib/plan.js.map +1 -1
- package/lib/proposals.d.ts.map +1 -1
- package/lib/proposals.js +25 -8
- package/lib/proposals.js.map +1 -1
- package/lib/sections.js +1 -1
- package/lib/sections.js.map +1 -1
- package/lib/ship.d.ts.map +1 -1
- package/lib/ship.js +9 -1
- package/lib/ship.js.map +1 -1
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.js +14 -10
- package/lib/shipment.js.map +1 -1
- package/lib/snapshot.d.ts.map +1 -1
- package/lib/snapshot.js +2 -1
- package/lib/snapshot.js.map +1 -1
- package/lib/stamp.js +1 -1
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +12 -2
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +11 -10
- package/lib/sync.js.map +1 -1
- package/lib/vars.d.ts.map +1 -1
- package/lib/vars.js +2 -3
- package/lib/vars.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.9.1.tgz +0 -0
- package/rt-tools-agent-kit-0.8.3.tgz +0 -0
package/README.md
CHANGED
|
@@ -293,6 +293,27 @@ 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
|
+
npx agent-kit enroll --token <токен> # положить токен, выданный в админке приёма
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Обмен по коду.** Код одноразовый и годен ограниченное время; имя дерева называет приглашение,
|
|
305
|
+
а не обращение. Адрес приёма без TLS команда отбивает у себя, не доходя до сети: токен уходит
|
|
306
|
+
единственным ответом на обращение.
|
|
307
|
+
|
|
308
|
+
**Выданный токен.** Владелец приёма выдаёт токен прямо в админке и передаёт его человеку;
|
|
309
|
+
обменивать не на что, и в сеть команда не идёт вовсе — ни адрес приёма, ни TLS этому пути не
|
|
310
|
+
нужны.
|
|
311
|
+
|
|
312
|
+
Два довода вместе не идут, и ни одного — тоже: отказ называет оба пути. Токен ложится одинаково,
|
|
313
|
+
каким бы путём ни пришёл: файл, названный ключом `token`, правами «читает только владелец файла».
|
|
314
|
+
Лежащий токен команда молча не перезаписывает ни на одном из путей — для этого есть довод
|
|
315
|
+
`--force`.
|
|
316
|
+
|
|
296
317
|
Груз уезжает при каждом прогоне тремя родами: **сводка наблюдений со снимком надстроек**,
|
|
297
318
|
**предложения** с адресом «пакет» — когда они есть — и **разборы происшествий**. Прогон без
|
|
298
319
|
замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево
|
|
@@ -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(
|
|
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}`));
|