@rt-tools/agent-kit 0.8.1 → 0.8.2
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 +24 -19
- package/assets/agents/qa-engineer.md +1 -1
- package/assets/checks/board.github.mjs +56 -17
- package/assets/checks/check-board.github.mjs +49 -5
- package/assets/checks/task-new.github.mjs +33 -5
- package/assets/commands/agent-kit-digest.md +10 -5
- package/assets/commands/next-session.md +4 -4
- package/assets/commands/skill-curator.md +11 -9
- package/assets/defaults/gate-map.sh +11 -4
- package/assets/defaults/project.sh +46 -0
- package/assets/docs/GLOSSARY.md +28 -26
- package/assets/hooks/docs-guard.sh +19 -3
- package/assets/hooks/git-guard-delivery.sh +106 -13
- package/assets/hooks/proposal-guard.sh +93 -0
- package/assets/hooks/reuse-first-guard.sh +68 -14
- package/assets/hooks/skill-gate-layers.sh +1 -1
- package/assets/hooks/skill-gate.sh +26 -0
- package/assets/hooks/task-flow-guard.sh +44 -18
- package/assets/hooks/window-fill-guard.sh +1 -1
- package/assets/laws/delivery.md +13 -7
- package/assets/laws/project-documentation.md +21 -0
- package/assets/laws/verifiability.md +6 -1
- package/assets/laws/work-conduct.md +67 -3
- package/assets/patterns/git-workflow-commit.azure.md +10 -2
- package/assets/patterns/git-workflow-commit.github.md +15 -2
- package/assets/patterns/git-workflow-commit.gitlab.md +10 -2
- package/assets/patterns/git-workflow-merge.md +1 -1
- package/assets/patterns/spec-driven-domain.md +7 -1
- package/assets/patterns/spec-driven-rule.md +6 -0
- package/assets/patterns/task-flow-close.md +78 -9
- package/assets/patterns/task-flow-handoff.md +27 -4
- package/assets/patterns/task-flow-resume.md +23 -5
- package/assets/patterns/task-flow-start.md +5 -1
- package/assets/rules/browser-verification.md +10 -1
- package/assets/rules/doc-style.md +13 -5
- package/assets/rules/git-workflow.azure.md +12 -7
- package/assets/rules/git-workflow.github.md +31 -13
- package/assets/rules/git-workflow.gitlab.md +12 -7
- package/assets/rules/spec-driven.md +4 -0
- package/assets/rules/task-flow.md +47 -22
- package/assets/rules/testing.md +19 -0
- package/assets/rules/typescript-conventions.md +12 -0
- package/assets/skills/agent-kit-extend.md +24 -0
- package/assets/skills/agent-kit.md +54 -6
- package/assets/traits.json +14 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +31 -16
- 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 +17 -0
- package/lib/argv.d.ts.map +1 -0
- package/lib/argv.js +44 -0
- package/lib/argv.js.map +1 -0
- package/lib/cargo.d.ts +88 -0
- package/lib/cargo.d.ts.map +1 -0
- package/lib/cargo.js +16 -0
- package/lib/cargo.js.map +1 -0
- package/lib/catalog.d.ts +18 -1
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +12 -2
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +0 -26
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +78 -122
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +37 -0
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +42 -1
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +28 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +20 -0
- package/lib/config.js.map +1 -1
- package/lib/ship.d.ts +39 -0
- package/lib/ship.d.ts.map +1 -0
- package/lib/ship.js +87 -0
- package/lib/ship.js.map +1 -0
- package/lib/shipment.d.ts +60 -0
- package/lib/shipment.d.ts.map +1 -0
- package/lib/shipment.js +247 -0
- package/lib/shipment.js.map +1 -0
- package/lib/snapshot.d.ts +30 -0
- package/lib/snapshot.d.ts.map +1 -0
- package/lib/snapshot.js +73 -0
- package/lib/snapshot.js.map +1 -0
- package/lib/traits.d.ts +32 -0
- package/lib/traits.d.ts.map +1 -0
- package/lib/traits.js +82 -0
- package/lib/traits.js.map +1 -0
- package/package.json +6 -2
- package/rt-tools-agent-kit-0.8.2.tgz +0 -0
- package/lib/submit.d.ts +0 -24
- package/lib/submit.d.ts.map +0 -1
- package/lib/submit.js +0 -26
- package/lib/submit.js.map +0 -1
- package/rt-tools-agent-kit-0.8.1.tgz +0 -0
- /package/assets/rules/{entity-conventions.md → entity-conventions.needs-admin.md} +0 -0
- /package/assets/rules/{observability.md → observability.needs-app.md} +0 -0
package/README.md
CHANGED
|
@@ -289,36 +289,41 @@ npx agent-kit stats --json # то же машиночитаемо
|
|
|
289
289
|
|
|
290
290
|
```bash
|
|
291
291
|
npx agent-kit propose --dry-run # что уехало бы
|
|
292
|
-
npx agent-kit propose #
|
|
292
|
+
npx agent-kit propose # отправить груз в приём
|
|
293
293
|
```
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
Груз уезжает при каждом прогоне тремя родами: **сводка наблюдений со снимком надстроек**,
|
|
296
|
+
**предложения** с адресом «пакет» — когда они есть — и **разборы происшествий**. Прогон без
|
|
297
|
+
замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево
|
|
298
|
+
переопределило. Отправленное предложение помечается в том же файле и второй раз не уезжает.
|
|
298
299
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
Уезжает груз в **закрытый приём**, а не в открытую очередь работ: сводка говорит о рабочих
|
|
301
|
+
привычках команды — чем пользуются, обо что спотыкаются, сколько раз признавали промах. Адрес
|
|
302
|
+
приёма объявлен настройкой дерева, а не зашит в код пакета.
|
|
303
|
+
|
|
304
|
+
Перед отправкой сводка и тексты предложений сверяются на адрес дерева — абсолютный путь, имя
|
|
305
|
+
корня, адрес удалённого репозитория. Нашлось — отказ с номером строки, и отбивается вся отправка
|
|
306
|
+
целиком: «уехало два из трёх» читается как «всё в порядке». Разбор происшествия проверкой не
|
|
307
|
+
накрыт: он по устройству называет файлы дерева, где промах случился.
|
|
303
308
|
|
|
304
309
|
### Что для этого нужно
|
|
305
310
|
|
|
306
|
-
|
|
307
|
-
|
|
311
|
+
Наблюдения и сводка живут целиком на машине; в сеть ходит только отправка, и только по команде
|
|
312
|
+
человека. Приём — своя служба, а не чужая: она поднимается там, где решит владелец пакета.
|
|
308
313
|
|
|
309
314
|
| Где | Что нужно |
|
|
310
315
|
| --- | --- |
|
|
311
316
|
| у потребителя | `jq` — его требуют и сами гарды |
|
|
312
|
-
| у потребителя |
|
|
313
|
-
|
|
|
317
|
+
| у потребителя | адрес приёма ключом `intake` в `.claude/rt-kit.json` |
|
|
318
|
+
| у потребителя | токен дерева в файле вне дерева, названном ключом `token` |
|
|
319
|
+
| у приёма | заведённое дерево: токен выдаёт его команда и печатает один раз |
|
|
314
320
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
321
|
+
Признак, которым дерево представляется приёму, считается хешем от адреса его удалённого
|
|
322
|
+
репозитория: адрес по нему не восстанавливается, а у двух рабочих копий одного репозитория он
|
|
323
|
+
один. Репозитория нет — признак называется ключом `tree`.
|
|
318
324
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
три сам.
|
|
325
|
+
Адреса нет, токена нет, токен отозван — отправка отказывает ненулевым кодом и называет, чего
|
|
326
|
+
именно не хватило и где это объявляется. Молчаливая отправка выглядела бы работающей.
|
|
322
327
|
|
|
323
328
|
## Роли и конвейеры
|
|
324
329
|
|
|
@@ -356,7 +361,7 @@ gh label create agent-kit-feedback --description 'Предложение по с
|
|
|
356
361
|
| `/agent-kit-digest` | `commands/agent-kit-digest.md` | сводит накопленные предложения и наблюдения в правки ресурсов |
|
|
357
362
|
|
|
358
363
|
`/next-session` приводит дерево к главной ветке — переходом на неё, если работа шла по правилу и
|
|
359
|
-
|
|
364
|
+
PR влит, и вливанием в текущую ветку во всех прочих случаях, — снимает влитые локальные
|
|
360
365
|
ветки, называет невлитые и пишет передачу для следующего захода. Незакоммиченная правка
|
|
361
366
|
останавливает её до первого действия; поставки она не касается.
|
|
362
367
|
|
|
@@ -8,7 +8,7 @@ tools: Read, Grep, Glob, Bash, Write, Edit, Skill, mcp__claude-in-chrome__select
|
|
|
8
8
|
`CLAUDE.md` и в правилах `testing` и `browser-verification`, а не предполагай. Отвечаешь
|
|
9
9
|
**по-русски**.
|
|
10
10
|
|
|
11
|
-
Твоя задача — найти, где сделанное не работает, а не подтвердить, что работает.
|
|
11
|
+
Твоя задача — найти, где сделанное не работает, а не подтвердить, что работает. PR без
|
|
12
12
|
единой находки допустим только тогда, когда ты честно пытался её получить.
|
|
13
13
|
|
|
14
14
|
## Чего делать нельзя
|
|
@@ -45,7 +45,7 @@ export const STATUS_FIELD_ID = BOARD.statusFieldId ?? '';
|
|
|
45
45
|
export const STATUS_OPTIONS = BOARD.statusOptions ?? {};
|
|
46
46
|
/** Колонка вновь заведённой задачи */
|
|
47
47
|
export const BACKLOG_OPTION_ID = STATUS_OPTIONS.backlog?.id ?? '';
|
|
48
|
-
/** Колонка задачи, взятой в работу, и задачи,
|
|
48
|
+
/** Колонка задачи, взятой в работу, и задачи, PR по которой ждёт разбора */
|
|
49
49
|
export const IN_PROGRESS_STATUS = 'in-progress';
|
|
50
50
|
export const IN_REVIEW_STATUS = 'in-review';
|
|
51
51
|
/** Учётная запись машинной работы — та же, от которой идут коммиты */
|
|
@@ -69,12 +69,6 @@ if (PROJECT_ID && !TASK_KEY) {
|
|
|
69
69
|
}
|
|
70
70
|
/** Кого запрашивают на разбор: без ревьювера PR не попадает во входящие владельца. */
|
|
71
71
|
export const REVIEWER = BOARD.reviewer ?? '';
|
|
72
|
-
/**
|
|
73
|
-
* Где лежит токен машинной учётной записи — так, как это назвало дерево. Идёт в текст отказа:
|
|
74
|
-
* зашитый путь послал бы чужое дерево заводить файл, который никто не читает.
|
|
75
|
-
*/
|
|
76
|
-
export const TOKEN_PATH = BOARD.tokenPath || 'путь не назван в .claude/rt-kit/checks.json';
|
|
77
|
-
|
|
78
72
|
const BOT_TOKEN_FILE = BOARD.tokenPath ? BOARD.tokenPath.replace(/^~/, homedir()) : '';
|
|
79
73
|
|
|
80
74
|
/**
|
|
@@ -89,7 +83,11 @@ function ghBinary() {
|
|
|
89
83
|
return existsSync(homebrew) ? homebrew : 'gh';
|
|
90
84
|
}
|
|
91
85
|
|
|
92
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Токен машинной записи лежит вне репозитория и в вывод не попадает. Его отсутствие — не отказ:
|
|
88
|
+
* дерево, не назвавшее токена в `board.tokenPath`, работает с очередью учётной записью, под
|
|
89
|
+
* которой залогинен клиент хостинга.
|
|
90
|
+
*/
|
|
93
91
|
export function botToken() {
|
|
94
92
|
if (!existsSync(BOT_TOKEN_FILE)) {
|
|
95
93
|
return null;
|
|
@@ -172,7 +170,7 @@ export function fetchBoard(options) {
|
|
|
172
170
|
|
|
173
171
|
/**
|
|
174
172
|
* Перевод задачи в другую колонку. Состояние задачи на борде — единственное, по чему
|
|
175
|
-
* видно ход работы: ветку и открытый
|
|
173
|
+
* видно ход работы: ветку и открытый PR борда сама не читает.
|
|
176
174
|
*/
|
|
177
175
|
export function moveTask(number, status, options) {
|
|
178
176
|
const option = STATUS_OPTIONS[status];
|
|
@@ -284,11 +282,53 @@ export function taskState(number, options) {
|
|
|
284
282
|
onBoard: item !== undefined,
|
|
285
283
|
status: item?.status ?? null,
|
|
286
284
|
assigned: issue.assignees.length > 0,
|
|
285
|
+
assignees: issue.assignees.map((assignee) => assignee.login),
|
|
287
286
|
numbered: numberFromTitle(issue.title) === issue.number,
|
|
288
287
|
labels: issue.labels.map((label) => label.name),
|
|
289
288
|
};
|
|
290
289
|
}
|
|
291
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Ответ очереди работ о заведённой задаче, сложенный в строки, и приговор: обеспечена работа
|
|
293
|
+
* или нет.
|
|
294
|
+
*
|
|
295
|
+
* Отделено от вызовов сети намеренно. Заведение кончается не выводом команды, а ответом
|
|
296
|
+
* очереди, и решение о том, что напечатать и чем кончиться, — это то самое место, где
|
|
297
|
+
* шестнадцать задач подряд прошли как успешные, не попав в очередь ни одна. Внутри вызовов
|
|
298
|
+
* сети оно проверяется только живой бордой, то есть не проверяется никогда.
|
|
299
|
+
*
|
|
300
|
+
* `state` — то, что вернул `taskState`, либо `{ offline: <причина> }`, если спросить не удалось.
|
|
301
|
+
*/
|
|
302
|
+
export function describeTaskState(number, state) {
|
|
303
|
+
if (state?.offline) {
|
|
304
|
+
return {
|
|
305
|
+
ok: false,
|
|
306
|
+
lines: [
|
|
307
|
+
`в очереди работ: спросить не удалось — ${state.offline}`,
|
|
308
|
+
'состояние очереди неизвестно, и заведённым это не считается',
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (!state?.exists) {
|
|
313
|
+
return { ok: false, lines: [`задачи #${number} у хостинга нет — заведение не состоялось`] };
|
|
314
|
+
}
|
|
315
|
+
if (!state.onBoard) {
|
|
316
|
+
return {
|
|
317
|
+
ok: false,
|
|
318
|
+
lines: [`в очереди работ: НЕТ`, 'задача, которой нет в очереди, работой не обеспечена — по ней никто не придёт'],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const column = state.status ? `колонка «${state.status}»` : 'колонки нет';
|
|
323
|
+
if (!state.assigned) {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
lines: [`в очереди работ: ${column}, исполнителя нет`, 'ничья задача стоит в очереди невидимой для того, кто её делает'],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return { ok: true, lines: [`в очереди работ: ${column}, исполнитель ${state.assignees.join(', ')}`] };
|
|
330
|
+
}
|
|
331
|
+
|
|
292
332
|
const isEntryPoint = process.argv[1] && import.meta.url === `file://${process.argv[1]}`;
|
|
293
333
|
if (isEntryPoint && process.argv[2] === 'task') {
|
|
294
334
|
try {
|
|
@@ -303,9 +343,12 @@ if (isEntryPoint && process.argv[2] === 'task') {
|
|
|
303
343
|
}
|
|
304
344
|
}
|
|
305
345
|
|
|
306
|
-
// Перевод колонки правит
|
|
307
|
-
//
|
|
308
|
-
//
|
|
346
|
+
// Перевод колонки правит борду. Токен машинной записи здесь необязателен: не назвавшее его
|
|
347
|
+
// дерево правит борду учётной записью, под которой залогинен клиент хостинга. Требование
|
|
348
|
+
// токена держало бы очередь работ у дерева, машинной записи не заводившего, и у дерева, чью
|
|
349
|
+
// запись ограничил хостинг. Отсутствие связи при этом — по-прежнему отказ, а не пропуск:
|
|
350
|
+
// непереставленная задача молча остаётся в прежней колонке, и расхождение всплывает только
|
|
351
|
+
// сверкой очереди.
|
|
309
352
|
if (isEntryPoint && process.argv[2] === 'move') {
|
|
310
353
|
const number = Number(process.argv[3]);
|
|
311
354
|
const status = process.argv[4];
|
|
@@ -313,11 +356,7 @@ if (isEntryPoint && process.argv[2] === 'move') {
|
|
|
313
356
|
console.error(`board: нужен номер задачи и колонка — node tools/board.mjs move 263 ${IN_PROGRESS_STATUS}`);
|
|
314
357
|
process.exit(1);
|
|
315
358
|
}
|
|
316
|
-
const token = botToken();
|
|
317
|
-
if (!token) {
|
|
318
|
-
console.error(`board: нет токена бота (${TOKEN_PATH}) — борда правится машинной учётной записью`);
|
|
319
|
-
process.exit(1);
|
|
320
|
-
}
|
|
359
|
+
const token = botToken() ?? undefined;
|
|
321
360
|
try {
|
|
322
361
|
const moved = moveTask(number, status, { token });
|
|
323
362
|
console.log(`#${number}: ${moved.from ?? 'вне колонок'} → ${moved.to}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Сверка очереди работ с тем, что закон о поставке требует от задачи и её
|
|
3
|
+
* Сверка очереди работ с тем, что закон о поставке требует от задачи и её PR.
|
|
4
4
|
*
|
|
5
5
|
* Гард поставки отбивает промах в момент, когда его совершают, но действует только
|
|
6
6
|
* на команды агента: тикет, заведённый мимо него, и PR, открытый руками, он не
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* PR, — а проверка, требующая невыполнимого, обходится, а не исполняется. Имя ветки
|
|
16
16
|
* стережёт гард в момент `git checkout -b` и `gh pr create`.
|
|
17
17
|
*
|
|
18
|
-
* Колонку задачи судит по её
|
|
18
|
+
* Колонку задачи судит по её PR: открытый PR означает разбор, отсутствие — нет.
|
|
19
19
|
* Момент, когда задачу берут в работу, отсюда не виден вовсе — ветки на борде нет, —
|
|
20
20
|
* и «In progress» здесь не требуется ни от кого.
|
|
21
21
|
*
|
|
@@ -29,13 +29,16 @@ import { join } from 'node:path';
|
|
|
29
29
|
|
|
30
30
|
import {
|
|
31
31
|
IN_REVIEW_STATUS,
|
|
32
|
+
OWNER,
|
|
32
33
|
OfflineError,
|
|
34
|
+
REPO,
|
|
33
35
|
STATUS_OPTIONS,
|
|
34
36
|
TASK_KEY,
|
|
35
37
|
botToken,
|
|
36
38
|
fetchBoard,
|
|
37
39
|
fetchIssues,
|
|
38
40
|
fetchOpenPulls,
|
|
41
|
+
gh,
|
|
39
42
|
numberFromTaskDir,
|
|
40
43
|
numberFromTitle,
|
|
41
44
|
taskDirs,
|
|
@@ -73,6 +76,34 @@ function closesNumbers(body) {
|
|
|
73
76
|
return [...String(body ?? '').matchAll(/\bCloses\s+#(\d+)\b/gi)].map((match) => Number(match[1]));
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Строка обхода в теле PR. Форма та же, что читает гард поставки: строку она начинает и
|
|
81
|
+
* подстановки не принимает — иначе текст, называющий эту строку, снимает требование сам собой.
|
|
82
|
+
*/
|
|
83
|
+
const FOLDER_SKIP = /^[ \t]*Task-folder-skip:[ \t]*[^\s<"'][^\s"']{2,}/im;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Везёт ли ветка PR папку своей задачи.
|
|
87
|
+
*
|
|
88
|
+
* Спрашивается ветка, а не рабочее дерево: папка, снесённая на машине и не закоммиченная,
|
|
89
|
+
* въедет вместе с веткой. Локальных ссылок тут мало — ветка PR может быть не подтянута
|
|
90
|
+
* сюда вовсе, — поэтому содержимое берётся у хостинга. Отказ «нет такого пути» означает, что
|
|
91
|
+
* папки нет; всё остальное поднимается выше и разбирается как отсутствие связи.
|
|
92
|
+
*/
|
|
93
|
+
function folderInBranch(branch, options) {
|
|
94
|
+
const path = `${CONFIG.tasksDir}/${branch}`;
|
|
95
|
+
try {
|
|
96
|
+
gh(['api', `repos/${OWNER}/${REPO}/contents/${path}?ref=${encodeURIComponent(branch)}`, '--jq', 'length'], options);
|
|
97
|
+
return path;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error instanceof OfflineError) {
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
76
107
|
let checked = { issues: 0, pulls: 0 };
|
|
77
108
|
|
|
78
109
|
// Черновики судятся по диску и потому проверяются всегда: связи для этого не нужно.
|
|
@@ -88,7 +119,7 @@ try {
|
|
|
88
119
|
checked = { issues: issues.length, pulls: pulls.length };
|
|
89
120
|
|
|
90
121
|
for (const item of board.foreign) {
|
|
91
|
-
report(`борда: ${item} — на борде стоят задачи, а не
|
|
122
|
+
report(`борда: ${item} — на борде стоят задачи, а не PR о них`);
|
|
92
123
|
}
|
|
93
124
|
// Борду проверяем только у открытых задач. Закрытая ушла из очереди мержем, и колонки под
|
|
94
125
|
// неё у борды нет: строку о ней нечем закрыть. Шесть таких строк висели в каждом прогоне и
|
|
@@ -124,9 +155,22 @@ try {
|
|
|
124
155
|
} else {
|
|
125
156
|
claimed.set(titleNumber, pull.number);
|
|
126
157
|
}
|
|
158
|
+
|
|
159
|
+
// Папка задачи, лежащая в ветке открытого PR, — единственное расхождение, которое
|
|
160
|
+
// сверка обязана назвать ДО слияния: гард судит её на слиянии, а слияние нажимает
|
|
161
|
+
// человек в браузере, где хуков нет вовсе. Сказанная после, эта строка уже не чинится
|
|
162
|
+
// тем же PR — работа перешла дальше, и на разбор заводится вторая задача.
|
|
163
|
+
if (!FOLDER_SKIP.test(String(pull.body ?? '')) && pull.headRefName) {
|
|
164
|
+
const folder = folderInBranch(pull.headRefName, options);
|
|
165
|
+
if (folder !== null) {
|
|
166
|
+
report(
|
|
167
|
+
`PR #${pull.number}: ветка везёт папку задачи «${folder}/» — разбери её этим же PR или поставь в тело строку «Task-folder-skip: <причина>»`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
127
171
|
}
|
|
128
172
|
|
|
129
|
-
// Колонка задачи и её
|
|
173
|
+
// Колонка задачи и её PR сверяются в обе стороны: открытый PR при задаче в «Backlog»
|
|
130
174
|
// читается как работа, к которой не приступали, а «In review» без открытого PR — как
|
|
131
175
|
// разбор, которого никто не ждёт.
|
|
132
176
|
for (const issue of open) {
|
|
@@ -134,7 +178,7 @@ try {
|
|
|
134
178
|
const pull = claimed.get(issue.number);
|
|
135
179
|
if (pull !== undefined && status !== IN_REVIEW) {
|
|
136
180
|
report(
|
|
137
|
-
`#${issue.number}:
|
|
181
|
+
`#${issue.number}: PR #${pull} открыт, а задача стоит «${status ?? 'вне колонок'}» — npm run task:move -- ${issue.number} ${IN_REVIEW_STATUS}`
|
|
138
182
|
);
|
|
139
183
|
}
|
|
140
184
|
if (pull === undefined && status === IN_REVIEW) {
|
|
@@ -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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
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
|
публикует. Скажи, что накопилось на выпуск, и остановись.
|
|
@@ -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
|
вторая запись об одном и том же.
|
|
@@ -61,23 +61,25 @@ cp .claude/rt-kit/templates/proposal.md .claude/rt-kit/proposals/$(date +%F)-<в
|
|
|
61
61
|
Дальше — по блоку на предложение, заголовком `## <адрес> · <ресурс>`. Адрес роль уже поставила,
|
|
62
62
|
твоё дело — не потерять его и не переписать текст своими словами.
|
|
63
63
|
|
|
64
|
-
Файл читает `agent-kit propose`: по заголовку он отбирает то, что уезжает в
|
|
65
|
-
|
|
64
|
+
Файл читает `agent-kit propose`: по заголовку он отбирает то, что уезжает в приём. Блок без
|
|
65
|
+
адреса в заголовке не уедет никуда и останется лежать молча.
|
|
66
66
|
|
|
67
67
|
## 5. Отправь то, что адресовано пакету
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
70
|
npx agent-kit propose --dry-run # что уехало бы
|
|
71
|
-
npx agent-kit propose #
|
|
71
|
+
npx agent-kit propose # отправить груз в приём
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
не
|
|
74
|
+
Груз уезжает при каждом прогоне: сводка наблюдений со снимком надстроек, предложения с адресом
|
|
75
|
+
«пакет» — когда они есть, — и разборы происшествий. Прогон без замечаний тоже говорит, чем
|
|
76
|
+
пользовались, чем не пользовались ни разу и что дерево переопределило. Отправленное предложение
|
|
77
|
+
помечается в том же файле и второй раз не уезжает.
|
|
77
78
|
|
|
78
|
-
Отправка отказывает, если
|
|
79
|
-
чужой репозиторий. Это не придирка:
|
|
80
|
-
обходи проверку.
|
|
79
|
+
Отправка отказывает, если адрес этого дерева нашёлся в сводке или в тексте предложения — путь,
|
|
80
|
+
имя корня, чужой репозиторий. Это не придирка: груз уезжает наружу целиком. Правь текст, а не
|
|
81
|
+
обходи проверку. Разбор происшествия проверкой не накрыт: он по устройству называет файлы
|
|
82
|
+
дерева, где промах случился.
|
|
81
83
|
|
|
82
84
|
## 6. Отдай результат владельцу
|
|
83
85
|
|
|
@@ -40,6 +40,13 @@ skill_for_default() {
|
|
|
40
40
|
case "$kind" in
|
|
41
41
|
edit)
|
|
42
42
|
case "$target" in
|
|
43
|
+
# Надстройка над разложенным текстом — правка того же текста: путь другой, а
|
|
44
|
+
# предмет тот же. Ветка стоит первой, потому что путь надстройки кончается тем
|
|
45
|
+
# же именем файла, что и разложенная копия, и ветки ниже разобрали бы её по
|
|
46
|
+
# расширению — то есть правилом формулировок вместо правила устройства.
|
|
47
|
+
*/.claude/rt-kit/overrides/laws/* | */.claude/rt-kit/overrides/rules/* | */.claude/rt-kit/overrides/patterns/*)
|
|
48
|
+
printf '%s\n' 'spec-driven' ;;
|
|
49
|
+
|
|
43
50
|
# Правило и паттерн — такая же договорённость, как спек: обязательные разделы,
|
|
44
51
|
# утверждение с привязкой, граница между статьёй закона и утверждением правила.
|
|
45
52
|
# Ветка стоит раньше общего исключения и раньше `*.md`: под исключением текст
|
|
@@ -138,9 +145,9 @@ skill_for_default() {
|
|
|
138
145
|
|| rt_gate_invokes "$target" "[^[:space:]]*task:new" \
|
|
139
146
|
|| rt_gate_invokes "$target" "prisma[[:space:]]+(migrate|db)"; then
|
|
140
147
|
printf '%s\n' 'git-workflow'
|
|
141
|
-
# Правка тела
|
|
148
|
+
# Правка тела PR через клиент хостинга ловится двумя признаками сразу — вызовом
|
|
142
149
|
# клиента И адресом запроса: одного слова о заявке мало, оно попадает в строку любой
|
|
143
|
-
# команды, которая о ней пишет. Тело
|
|
150
|
+
# команды, которая о ней пишет. Тело PR не читает ни одна проверка, и утверждение
|
|
144
151
|
# о дереве стареет в нём молча.
|
|
145
152
|
elif rt_gate_invokes "$target" "(gh|glab)[[:space:]]+api" \
|
|
146
153
|
&& printf '%s' "$target" | grep -qE '(-X|--method)[[:space:]]+(PATCH|PUT).*(pulls|merge_requests)/[0-9]+'; then
|
|
@@ -154,8 +161,8 @@ skill_for_default() {
|
|
|
154
161
|
printf '%s\n' 'git-workflow'
|
|
155
162
|
fi
|
|
156
163
|
|
|
157
|
-
# Слияние
|
|
158
|
-
# тем же
|
|
164
|
+
# Слияние PR — последний момент, когда папку закрытой задачи ещё можно разобрать
|
|
165
|
+
# тем же PR: после слияния сверка очереди её видит, а отвечать за неё уже
|
|
159
166
|
# некому. Требуется ВТОРЫМ слоем, дополнительно к правилу поставки.
|
|
160
167
|
rt_gate_invokes "$target" "(gh[[:space:]]+pr|glab[[:space:]]+mr)[[:space:]]+merge" \
|
|
161
168
|
&& printf '%s\n' 'task-flow'
|
|
@@ -166,6 +166,36 @@ rt_is_app_code_default() {
|
|
|
166
166
|
esac
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
# Пишет ли команда оболочки файл. Успех — да, и тогда пути из неё судятся тем же признаком,
|
|
170
|
+
# что и путь из вызова инструмента правки.
|
|
171
|
+
#
|
|
172
|
+
# Гарды подписаны на инструменты правки файла, и этого мало: ту же правку кладут командой —
|
|
173
|
+
# перенаправлением, `tee`, `sed -i`, интерпретатором с heredoc. Отбитая правка дважды за один
|
|
174
|
+
# заход легла именно так, и не увидел этого никто: в дереве она неотличима от положенной
|
|
175
|
+
# инструментом. Разбор — `2026-08-15-guard-denied-shell-wrote-anyway.md`.
|
|
176
|
+
#
|
|
177
|
+
# Список намеренно широк, и цена этого названа: команда чтения, в которой стоит имя
|
|
178
|
+
# интерпретатора, будет отбита наравне с командой правки. Узкий список стоил бы дороже —
|
|
179
|
+
# пропущенная форма записи возвращает обход целиком, а найти её можно только промахом.
|
|
180
|
+
rt_shell_writes_default() {
|
|
181
|
+
printf '%s' "$1" | grep -Eq \
|
|
182
|
+
'>>?[[:space:]]*[^|&>[:space:]]|\btee\b|\bsed\b[^|]*-i|\bperl\b[^|]*-i|\bpython3?\b|\bnode\b|\bruby\b|\bdd\b[^|]*of=|\bcp\b|\bmv\b|\brm\b|\btouch\b|\btruncate\b|\binstall\b|\bpatch\b|\bgit[[:space:]]+(checkout|restore|apply|stash)\b'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Пути, названные командой оболочки. Печатает по одному в строке; судит их зовущий.
|
|
186
|
+
#
|
|
187
|
+
# Разбирать оболочку по-настоящему нечем — здесь и не разбирают: из текста вынимается всё, что
|
|
188
|
+
# похоже на путь, и каждое отдаётся признаку. Лишнее он отсеет сам, а пропущенное вернуло бы
|
|
189
|
+
# обход. Кавычки и heredoc снимаются заменой на пробел: путь внутри них тот же самый.
|
|
190
|
+
rt_shell_paths_default() {
|
|
191
|
+
printf '%s' "$1" \
|
|
192
|
+
| tr "\"'\`(),;=" ' ' \
|
|
193
|
+
| tr '[:space:]' '\n' \
|
|
194
|
+
| grep -E '^[A-Za-z0-9_@.-]*/[A-Za-z0-9_@./-]+$' \
|
|
195
|
+
| sed 's|^\./||' \
|
|
196
|
+
| sort -u
|
|
197
|
+
}
|
|
198
|
+
|
|
169
199
|
# Имя ветки, с которой разрешено открывать заявку на слияние: в имени стоит номер задачи.
|
|
170
200
|
# Приставка — либо род правки, либо метка очереди работ: обе формы носят номер, а он и нужен.
|
|
171
201
|
#
|
|
@@ -201,6 +231,20 @@ RT_BOARD_CHECK_CMD="${RT_BOARD_CHECK_CMD:-npm run check:board}"
|
|
|
201
231
|
# Учётная запись, которую ставят исполнителем. Умолчание молчит: у каждого дерева она своя.
|
|
202
232
|
RT_TASK_BOT="${RT_TASK_BOT:-}"
|
|
203
233
|
|
|
234
|
+
# Почта, которой подписан коммит машинной учётной записи. Целым значением, а не образцом:
|
|
235
|
+
# служебный адрес хостинга состоит из числа, логина и домена, а сопоставляется по числу — логин
|
|
236
|
+
# рядом с ним не сверяет никто. Образец «число, плюс, логин» прошёл бы с чужим числом, то есть
|
|
237
|
+
# ровно с тем промахом, ради которого гард поставки подпись и читает.
|
|
238
|
+
#
|
|
239
|
+
# Отсюда же он берёт логин машинной записи — левой частью адреса, до собаки и после плюса.
|
|
240
|
+
# Вторым свойством логин не объявляется: два объявления одного имени разошлись бы молча.
|
|
241
|
+
# Исполнитель задачи для этого не годится — там, где хостинг ограничил машинную запись,
|
|
242
|
+
# исполнителем ставят человека, а коммит остаётся машинным.
|
|
243
|
+
#
|
|
244
|
+
# Умолчание молчит, и тогда подпись не судится: своей машинной записи у пакета нет, а выдуманная
|
|
245
|
+
# отбивала бы работу в чужом дереве.
|
|
246
|
+
RT_COMMIT_EMAIL="${RT_COMMIT_EMAIL:-}"
|
|
247
|
+
|
|
204
248
|
# Состояние задачи одним объектом: exists, open, onBoard, assigned, numbered. Спрашивает
|
|
205
249
|
# помощника очереди работ — того же, которым пользуются сверка и команда заведения, чтобы
|
|
206
250
|
# гард и очередь одинаково понимали «задача в порядке». Нет узла, нет помощника, нет сети —
|
|
@@ -259,6 +303,8 @@ rt_lint_for() { rt_lint_for_default "$@"; }
|
|
|
259
303
|
rt_task_branch_ok() { rt_task_branch_ok_default "$@"; }
|
|
260
304
|
rt_reinvented_in() { rt_reinvented_in_default "$@"; }
|
|
261
305
|
rt_is_app_code() { rt_is_app_code_default "$@"; }
|
|
306
|
+
rt_shell_writes() { rt_shell_writes_default "$@"; }
|
|
307
|
+
rt_shell_paths() { rt_shell_paths_default "$@"; }
|
|
262
308
|
rt_qa_decorative() { rt_qa_decorative_default "$@"; }
|
|
263
309
|
rt_task_state() { rt_task_state_default "$@"; }
|
|
264
310
|
rt_report_body() { rt_report_body_default "$@"; }
|