@rt-tools/agent-kit 0.8.2 → 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.
- package/README.md +13 -0
- package/assets/agents/rules-reviewer.md +83 -0
- package/assets/checks/board.github.mjs +48 -1
- package/assets/checks/check-board.github.mjs +84 -1
- package/assets/checks/check-dupes.mjs +66 -6
- package/assets/checks/check-lib-layers.mjs +13 -524
- package/assets/checks/check-specs.mjs +61 -747
- 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 +26 -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/commands/feedback.md +95 -0
- package/assets/commands/rules-review.md +98 -0
- package/assets/commands/skill-curator.md +39 -22
- package/assets/defaults/project.sh +8 -0
- package/assets/docs/GLOSSARY.md +21 -20
- package/assets/hooks/git-guard-push-tests.sh +8 -4
- package/assets/hooks/reuse-first-guard.sh +16 -2
- 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/task-flow-guard.sh +16 -2
- package/assets/hooks/waiting-turn-guard.sh +116 -0
- package/assets/laws/delivery.md +35 -0
- package/assets/laws/project-documentation.md +18 -0
- package/assets/laws/work-conduct.md +25 -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/git-workflow-commit.azure.md +74 -2
- package/assets/patterns/git-workflow-commit.github.md +75 -2
- package/assets/patterns/git-workflow-commit.gitlab.md +75 -4
- package/assets/patterns/git-workflow-docker.md +30 -0
- package/assets/patterns/observability-record.md +9 -0
- package/assets/patterns/shared-code-new.md +2 -2
- package/assets/patterns/task-flow-close.md +160 -47
- package/assets/patterns/task-flow-handoff.md +1 -1
- package/assets/patterns/task-flow-resume.md +3 -3
- package/assets/patterns/task-flow-start.md +32 -5
- package/assets/rules/angular-patterns.md +22 -0
- package/assets/rules/api-layer.md +25 -0
- package/assets/rules/browser-verification.md +32 -0
- package/assets/rules/component-structure.md +21 -0
- package/assets/rules/dependencies.md +22 -0
- package/assets/rules/doc-style.md +24 -0
- package/assets/rules/entity-conventions.needs-admin.md +21 -0
- package/assets/rules/entity-models.md +21 -0
- package/assets/rules/git-workflow.azure.md +57 -1
- package/assets/rules/git-workflow.github.md +89 -2
- package/assets/rules/git-workflow.gitlab.md +56 -1
- package/assets/rules/lib-layers.md +29 -0
- package/assets/rules/lists.md +37 -10
- package/assets/rules/navigation.md +21 -0
- package/assets/rules/observability.needs-app.md +23 -0
- package/assets/rules/permissions.md +23 -0
- package/assets/rules/platform-access.md +21 -0
- package/assets/rules/reuse-first.md +20 -0
- package/assets/rules/seo.md +19 -0
- package/assets/rules/shared-code.md +20 -1
- package/assets/rules/spec-driven.md +32 -0
- package/assets/rules/styling-bem.md +19 -0
- package/assets/rules/task-flow.md +148 -18
- package/assets/rules/testing.md +62 -0
- package/assets/rules/translations.md +21 -0
- package/assets/rules/typescript-conventions.md +28 -0
- package/assets/samples/specs/_template/spec.md +83 -0
- package/assets/samples/tasks/_template/grill.md +28 -0
- package/assets/samples/tasks/_template/plan.md +39 -0
- package/assets/samples/tasks/_template/progress.md +23 -0
- package/assets/skills/agent-kit.md +52 -2
- package/assets/templates/proposal.md +21 -0
- package/assets/templates/rule.md +31 -2
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +115 -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 +153 -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 +12 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +11 -5
- package/lib/config.js.map +1 -1
- package/lib/enroll.d.ts +56 -0
- package/lib/enroll.d.ts.map +1 -0
- package/lib/enroll.js +123 -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.0.tgz +0 -0
- package/rt-tools-agent-kit-0.8.2.tgz +0 -0
package/README.md
CHANGED
|
@@ -63,6 +63,7 @@ npx agent-kit sync --check
|
|
|
63
63
|
| `agents`, `commands`, `workflows` | `.claude/` | роли, слеш-команды и многошаговые прогоны |
|
|
64
64
|
| `checks` | `tools/` | проверки, которые зовёт гейт |
|
|
65
65
|
| `templates` | `.claude/rt-kit/templates/` | формы правила, паттерна, компаньона и надстроек |
|
|
66
|
+
| `samples` | `docs/` | образцы, которые копируют в рабочий файл: папка задачи, спек домена |
|
|
66
67
|
|
|
67
68
|
Правило, паттерн и скил ложатся одинаково — все три скилы; различает их `kind` во вступлении
|
|
68
69
|
файла. Скил без закона стоит рядом с лестницей, а не в ней: он не про то, что должно быть верно
|
|
@@ -292,6 +293,18 @@ npx agent-kit propose --dry-run # что уехало бы
|
|
|
292
293
|
npx agent-kit propose # отправить груз в приём
|
|
293
294
|
```
|
|
294
295
|
|
|
296
|
+
Чтобы грузу было чем представиться, у дерева должен быть токен. Выдаётся он один раз — по
|
|
297
|
+
приглашению, которое владелец приёма выписал этому дереву:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
npx agent-kit enroll --code <код приглашения> # завести дерево и положить его токен
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Код одноразовый и годен ограниченное время; имя дерева называет приглашение, а не обращение.
|
|
304
|
+
Токен ложится в файл, названный ключом `token`, правами «читает только владелец файла»; лежащий
|
|
305
|
+
токен команда молча не перезаписывает — для этого есть довод `--force`. Адрес приёма без TLS
|
|
306
|
+
команда отбивает у себя, не доходя до сети: токен уходит единственным ответом на обращение.
|
|
307
|
+
|
|
295
308
|
Груз уезжает при каждом прогоне тремя родами: **сводка наблюдений со снимком надстроек**,
|
|
296
309
|
**предложения** с адресом «пакет» — когда они есть — и **разборы происшествий**. Прогон без
|
|
297
310
|
замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rules-reviewer
|
|
3
|
+
description: Читает семью текстов слоя правил целиком — закон, все правила под ним и все паттерны при них — и ищет то, чего не считает машина: два текста, говорящих об одном разное, и случай, которого не назвал ни один. Файлов не правит. Использовать перед выпуском новой редакции пакета правил и после правки закона или правила.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Ты читаешь семью текстов слоя правил и ищешь расхождения смысла. Отвечаешь **по-русски**.
|
|
8
|
+
|
|
9
|
+
Твой результат — список находок. Не правки: ты ничего не меняешь.
|
|
10
|
+
|
|
11
|
+
Промах в этих текстах уезжает ко всем деревьям разом и находит его тот, кто пошёл за правилом и
|
|
12
|
+
сделал не то. Считаемое — недостающий раздел, правило без паттерна, имя соседа, которому ничего
|
|
13
|
+
не отвечает — уже ловит проверка. Тебе остаётся то, что видно только чтением.
|
|
14
|
+
|
|
15
|
+
## Чего делать нельзя
|
|
16
|
+
|
|
17
|
+
- **Никаких git-команд вообще**, включая `status` и `diff`. Историю ведёт главный агент.
|
|
18
|
+
- Ничего не править: ни закон, ни правило, ни паттерн. Ты возвращаешь находки.
|
|
19
|
+
- Не пересказывать найденное своими словами: находка без дословной цитаты не проверяется ничем,
|
|
20
|
+
и человек не отличит настоящее расхождение от твоего прочтения.
|
|
21
|
+
- Не считать того, что уже считает проверка полноты текстов. Повтор её вывода вытесняет из
|
|
22
|
+
ответа то, ради чего тебя и звали.
|
|
23
|
+
- Не судить о дереве, в котором ты запущен. Тексты пакета переносятся, и что из них разложено
|
|
24
|
+
здесь — не их предмет.
|
|
25
|
+
|
|
26
|
+
## По чему идёшь
|
|
27
|
+
|
|
28
|
+
Семья — это **один закон, все правила под ним и все паттерны при этих правилах**. Имя закона
|
|
29
|
+
тебе даёт вызывающий. Пакет целиком не читается: текстов в нём больше десяти тысяч строк, и
|
|
30
|
+
прочитанные разом они дают крошку по каждому файлу вместо находок.
|
|
31
|
+
|
|
32
|
+
Правило принадлежит закону по полю `law:` в его шапке, паттерн правилу — по полю `rule:`.
|
|
33
|
+
Приставка имени ненадёжна: её несут не все.
|
|
34
|
+
|
|
35
|
+
**Все виды правила читаются, а не выбранный.** У одного ресурса бывает несколько редакций —
|
|
36
|
+
`git-workflow.github`, `git-workflow.gitlab`, `git-workflow.azure`. Дерево раскладывает одну, и
|
|
37
|
+
остальные не читает никто: разойдясь, они молчат до первого дерева, выбравшего другую.
|
|
38
|
+
|
|
39
|
+
**Суффикс требования видом не является.** `entity-conventions.needs-admin`,
|
|
40
|
+
`observability.needs-app` — правила, которые дерево берёт, только объявив нужную черту. В поле
|
|
41
|
+
`rule:` у паттерна стоит голое имя, без обоих суффиксов: собранная по имени с суффиксом семья
|
|
42
|
+
приходит без паттернов, и пустота эта выглядит как их отсутствие. Пришедшая к тебе семья без
|
|
43
|
+
единого паттерна — повод сказать об этом, а не молча разобрать что дали.
|
|
44
|
+
|
|
45
|
+
Ищешь два рода находок, и они разные.
|
|
46
|
+
|
|
47
|
+
**Расхождение — два места, говорящих об одном разное.** Правило требует того, что паттерн при
|
|
48
|
+
соседнем правиле запрещает. Закон называет одно число, правило — другое. Паттерн показывает
|
|
49
|
+
приём, который правило объявило отвергнутым. Сюда же — одно понятие под двумя именами и одно имя
|
|
50
|
+
над двумя понятиями.
|
|
51
|
+
|
|
52
|
+
**Пробел — случай, которого не назвал ни один текст семьи.** Статья закона, под которую ни в
|
|
53
|
+
одном правиле нет ни строки. Развилка, у которой описана одна ветка из двух. Отказ, о котором
|
|
54
|
+
сказано, что он бывает, и не сказано, что делать. Раздел «Чего из закона здесь нет», который
|
|
55
|
+
молчит о том, чего в дереве действительно нет.
|
|
56
|
+
|
|
57
|
+
**Граф хода — такое же место расхождения, как проза.** Он стоит в правиле разделом «Ход»
|
|
58
|
+
блоком `mermaid` и изображает тот же ход, что описан ниже словами: ветка графа, которой в прозе нет, и статья, до
|
|
59
|
+
которой по графу не дойти, — расхождение того же рода, что и два текста об одном. Правится он тем
|
|
60
|
+
же изменением, что и проза, и разойдясь, обе стороны читаются как действующие.
|
|
61
|
+
|
|
62
|
+
Три места, где расхождения заводятся чаще прочего, — проверь каждое:
|
|
63
|
+
|
|
64
|
+
- **«Чего из закона здесь нет»** — его не читает ни одна сверка, и неправда живёт в нём сколько
|
|
65
|
+
угодно: раздел говорит об отсутствии механизма, а механизм давно заведён, и заметить это может
|
|
66
|
+
только тот, кто пошёл его искать.
|
|
67
|
+
- **Числа** — счёт правил, статей, шагов, строк. Они стареют без единой правки рядом.
|
|
68
|
+
- **Ловушки** — их пишут по случаю и не перечитывают: приём, который они запрещают, мог с тех
|
|
69
|
+
пор стать рабочим.
|
|
70
|
+
|
|
71
|
+
## Что возвращаешь
|
|
72
|
+
|
|
73
|
+
Список находок, самая дорогая первой. Роды не смешивай — сперва расхождения, потом пробелы.
|
|
74
|
+
|
|
75
|
+
У **расхождения**: имена обоих ресурсов, обе цитаты дословно, и одной фразой — чем именно они
|
|
76
|
+
расходятся и что исполнитель сделает не так, пойдя за той или другой.
|
|
77
|
+
|
|
78
|
+
У **пробела**: имя ресурса, в котором его недостаёт, цитата места, где он должен был стоять
|
|
79
|
+
(или имя раздела, если места нет вовсе), и что случится, когда этот случай наступит. Второй
|
|
80
|
+
цитаты у пробела не бывает — не выдумывай её.
|
|
81
|
+
|
|
82
|
+
Находок нет — так и скажи. Пустой ответ дешевле выдуманного: по выдуманному правят настоящие
|
|
83
|
+
тексты.
|
|
@@ -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}`));
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
*
|
|
37
37
|
* Ненулевой код возврата и перечень расхождений.
|
|
38
38
|
*/
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
39
|
+
import { createRequire } from 'node:module';
|
|
40
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
41
|
+
import { dirname, join } from 'node:path';
|
|
41
42
|
|
|
42
43
|
import { allowlistOf, CONFIG, ROOT } from './rt-kit-checks.config.mjs';
|
|
43
44
|
|
|
@@ -76,6 +77,56 @@ function collectFiles(dir) {
|
|
|
76
77
|
return files;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Каталог объявлений внешнего пакета — разрешением модуля, а не путём в `node_modules`.
|
|
82
|
+
*
|
|
83
|
+
* Точек разрешения несколько: корень дерева и каждый его подпроект, объявивший этот пакет
|
|
84
|
+
* зависимостью. Пакет подпроекта в корне не лежит вовсе, и разрешение от корня его не находит;
|
|
85
|
+
* менеджер при этом вправе держать рядом несколько версий сразу, и обход хранилища по образцу
|
|
86
|
+
* пути выбрал бы ту, которую никто не ставит.
|
|
87
|
+
*
|
|
88
|
+
* Не нашлось — `null`, и внешние наборы просто не считаются: дерево без этого пакета должно
|
|
89
|
+
* получать сверку своих повторов, а не отказ чтения каталога.
|
|
90
|
+
*/
|
|
91
|
+
function resolveExternalDir({ package: name, dir }) {
|
|
92
|
+
for (const from of [ROOT, ...holdersOf(name)]) {
|
|
93
|
+
try {
|
|
94
|
+
const manifest = createRequire(join(from, 'package.json')).resolve(`${name}/package.json`);
|
|
95
|
+
const found = join(dirname(manifest), dir);
|
|
96
|
+
if (existsSync(found)) {
|
|
97
|
+
return found;
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// Эта точка пакета не видит — пробуется следующая.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Подпроекты, объявившие пакет зависимостью: их манифесты и есть точки разрешения. */
|
|
108
|
+
function holdersOf(name) {
|
|
109
|
+
const found = [];
|
|
110
|
+
for (const root of SOURCE_ROOTS) {
|
|
111
|
+
if (!existsSync(join(ROOT, root))) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const entry of readdirSync(join(ROOT, root), { withFileTypes: true })) {
|
|
115
|
+
const manifest = join(ROOT, root, entry.name, 'package.json');
|
|
116
|
+
if (!entry.isDirectory() || !existsSync(manifest)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const declared = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
120
|
+
const fields = [declared.dependencies, declared.peerDependencies, declared.devDependencies];
|
|
121
|
+
if (fields.some((field) => field?.[name])) {
|
|
122
|
+
found.push(join(ROOT, root, entry.name));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return found;
|
|
128
|
+
}
|
|
129
|
+
|
|
79
130
|
/** Корень либы: путь до каталога `src`. Повтор внутри одной либы повтором не считается */
|
|
80
131
|
function libOf(path) {
|
|
81
132
|
const parts = path.split('/');
|
|
@@ -116,8 +167,13 @@ const MIN_TABLE_PAIRS = 2;
|
|
|
116
167
|
/**
|
|
117
168
|
* Пакеты, чьи наборы считаются наравне с либами. Своё перечисление под уже
|
|
118
169
|
* объявленный там набор — такая же копия, как и между двумя либами.
|
|
170
|
+
*
|
|
171
|
+
* Имя пакета и каталог внутри него объявляет дерево; путь в `node_modules` здесь не
|
|
172
|
+
* зашивается. Пакет, объявленный зависимостью подпроекта, в корневом `node_modules` не лежит
|
|
173
|
+
* вовсе — менеджер держит его в своём хранилище, — и проверка кончалась отказом чтения
|
|
174
|
+
* каталога, не дойдя до сверки ни разу.
|
|
119
175
|
*/
|
|
120
|
-
const EXTERNAL_ENUM_SOURCES = [
|
|
176
|
+
const EXTERNAL_ENUM_SOURCES = CONFIG.externalEnums ?? [];
|
|
121
177
|
|
|
122
178
|
const exportsByName = new Map();
|
|
123
179
|
const settingsByName = new Map();
|
|
@@ -188,10 +244,14 @@ for (const path of SOURCE_ROOTS.flatMap((root) => collectFiles(root))) {
|
|
|
188
244
|
collectEnums(text, lib);
|
|
189
245
|
}
|
|
190
246
|
|
|
191
|
-
for (const
|
|
192
|
-
|
|
247
|
+
for (const source of EXTERNAL_ENUM_SOURCES) {
|
|
248
|
+
const dir = resolveExternalDir(source);
|
|
249
|
+
if (!dir) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
193
253
|
if (entry.isFile() && entry.name.endsWith('.d.ts')) {
|
|
194
|
-
collectEnums(readFileSync(join(
|
|
254
|
+
collectEnums(readFileSync(join(dir, entry.name), 'utf8'), `${source.package}/${source.dir}`);
|
|
195
255
|
}
|
|
196
256
|
}
|
|
197
257
|
}
|