@rt-tools/agent-kit 0.8.0 → 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.
Files changed (104) hide show
  1. package/README.md +24 -19
  2. package/assets/agents/qa-engineer.md +1 -1
  3. package/assets/checks/board.github.mjs +56 -17
  4. package/assets/checks/check-board.github.mjs +49 -5
  5. package/assets/checks/check-reuse.mjs +9 -7
  6. package/assets/checks/task-new.github.mjs +33 -5
  7. package/assets/commands/agent-kit-digest.md +10 -5
  8. package/assets/commands/next-session.md +4 -4
  9. package/assets/commands/skill-curator.md +11 -9
  10. package/assets/defaults/gate-map.sh +11 -4
  11. package/assets/defaults/project.sh +46 -0
  12. package/assets/docs/GLOSSARY.md +28 -26
  13. package/assets/hooks/docs-guard.sh +19 -3
  14. package/assets/hooks/git-guard-delivery.sh +106 -13
  15. package/assets/hooks/proposal-guard.sh +93 -0
  16. package/assets/hooks/reuse-first-guard.sh +68 -14
  17. package/assets/hooks/skill-gate-layers.sh +1 -1
  18. package/assets/hooks/skill-gate.sh +26 -0
  19. package/assets/hooks/task-flow-guard.sh +44 -18
  20. package/assets/hooks/window-fill-guard.sh +1 -1
  21. package/assets/laws/delivery.md +13 -7
  22. package/assets/laws/project-documentation.md +21 -0
  23. package/assets/laws/verifiability.md +6 -1
  24. package/assets/laws/work-conduct.md +67 -3
  25. package/assets/patterns/git-workflow-commit.azure.md +10 -2
  26. package/assets/patterns/git-workflow-commit.github.md +15 -2
  27. package/assets/patterns/git-workflow-commit.gitlab.md +10 -2
  28. package/assets/patterns/git-workflow-merge.md +1 -1
  29. package/assets/patterns/reuse-first-extend.md +12 -3
  30. package/assets/patterns/spec-driven-domain.md +7 -1
  31. package/assets/patterns/spec-driven-rule.md +6 -0
  32. package/assets/patterns/task-flow-close.md +78 -9
  33. package/assets/patterns/task-flow-handoff.md +27 -4
  34. package/assets/patterns/task-flow-resume.md +23 -5
  35. package/assets/patterns/task-flow-start.md +5 -1
  36. package/assets/rules/browser-verification.md +10 -1
  37. package/assets/rules/doc-style.md +13 -5
  38. package/assets/rules/git-workflow.azure.md +12 -7
  39. package/assets/rules/git-workflow.github.md +31 -13
  40. package/assets/rules/git-workflow.gitlab.md +12 -7
  41. package/assets/rules/reuse-first.md +1 -1
  42. package/assets/rules/spec-driven.md +4 -0
  43. package/assets/rules/task-flow.md +47 -22
  44. package/assets/rules/testing.md +19 -0
  45. package/assets/rules/typescript-conventions.md +12 -0
  46. package/assets/skills/agent-kit-extend.md +173 -0
  47. package/assets/skills/agent-kit.md +62 -10
  48. package/assets/traits.json +14 -0
  49. package/bin/agent-kit.d.ts.map +1 -1
  50. package/bin/agent-kit.js +31 -16
  51. package/bin/agent-kit.js.map +1 -1
  52. package/index.d.ts +1 -0
  53. package/index.d.ts.map +1 -1
  54. package/index.js +1 -0
  55. package/index.js.map +1 -1
  56. package/lib/argv.d.ts +17 -0
  57. package/lib/argv.d.ts.map +1 -0
  58. package/lib/argv.js +44 -0
  59. package/lib/argv.js.map +1 -0
  60. package/lib/cargo.d.ts +88 -0
  61. package/lib/cargo.d.ts.map +1 -0
  62. package/lib/cargo.js +16 -0
  63. package/lib/cargo.js.map +1 -0
  64. package/lib/catalog.d.ts +18 -1
  65. package/lib/catalog.d.ts.map +1 -1
  66. package/lib/catalog.js +12 -2
  67. package/lib/catalog.js.map +1 -1
  68. package/lib/commands.d.ts +0 -26
  69. package/lib/commands.d.ts.map +1 -1
  70. package/lib/commands.js +78 -122
  71. package/lib/commands.js.map +1 -1
  72. package/lib/companion.d.ts +37 -0
  73. package/lib/companion.d.ts.map +1 -1
  74. package/lib/companion.js +42 -1
  75. package/lib/companion.js.map +1 -1
  76. package/lib/config.d.ts +28 -0
  77. package/lib/config.d.ts.map +1 -1
  78. package/lib/config.js +20 -0
  79. package/lib/config.js.map +1 -1
  80. package/lib/ship.d.ts +39 -0
  81. package/lib/ship.d.ts.map +1 -0
  82. package/lib/ship.js +87 -0
  83. package/lib/ship.js.map +1 -0
  84. package/lib/shipment.d.ts +60 -0
  85. package/lib/shipment.d.ts.map +1 -0
  86. package/lib/shipment.js +247 -0
  87. package/lib/shipment.js.map +1 -0
  88. package/lib/snapshot.d.ts +30 -0
  89. package/lib/snapshot.d.ts.map +1 -0
  90. package/lib/snapshot.js +73 -0
  91. package/lib/snapshot.js.map +1 -0
  92. package/lib/traits.d.ts +32 -0
  93. package/lib/traits.d.ts.map +1 -0
  94. package/lib/traits.js +82 -0
  95. package/lib/traits.js.map +1 -0
  96. package/package.json +6 -2
  97. package/rt-tools-agent-kit-0.8.2.tgz +0 -0
  98. package/lib/submit.d.ts +0 -24
  99. package/lib/submit.d.ts.map +0 -1
  100. package/lib/submit.js +0 -26
  101. package/lib/submit.js.map +0 -1
  102. package/rt-tools-agent-kit-0.8.0.tgz +0 -0
  103. /package/assets/rules/{entity-conventions.md → entity-conventions.needs-admin.md} +0 -0
  104. /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
- ```bash
316
- gh label create agent-kit-feedback --description 'Предложение по слою правил, пришедшее из дерева'
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
- * Колонку задачи судит по её отчёту: открытый PR означает разбор, отсутствие — нет.
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
- // Колонка задачи и её отчёт сверяются в обе стороны: открытый PR при задаче в «Backlog»
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}: отчёт PR #${pull} открыт, а задача стоит «${status ?? 'вне колонок'}» — npm run task:move -- ${issue.number} ${IN_REVIEW_STATUS}`
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) {
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Отличий от гарда два. Первое: инвентарь кита не читается — гард спрашивает диск, потому что
16
16
  * отвечает одной правке, а сплошной проверке важно накопленное, и пропавший пакет молча
17
- * обнулял бы сводку. Второе: маркер `native-ok` снимает свою строку, а не весь файл.
17
+ * обнулял бы сводку. Второе: маркер `native-ok` снимает свою строку и следующую, а не весь файл.
18
18
  *
19
19
  * Накопленное к моменту заведения проверки лежит в tools/reuse-allowlist.json и отказом не
20
20
  * считается: гейт падает на новом расхождении, а старое остаётся видимым числом в сводке.
@@ -85,14 +85,16 @@ function judged(path) {
85
85
  }
86
86
 
87
87
  /**
88
- * Строка с маркером — осознанное отступление, названное автором. Снимается только она сама:
89
- * весь файл маркер не гасит, иначе один разрешённый случай прикрывал бы соседние.
88
+ * Маркер — осознанное отступление, названное автором. Снимается строка, где он стоит, и та, что
89
+ * идёт следом: в разметке маркер ставится комментарием над кодом, потому что форматировщик
90
+ * разносит длинный тег по строкам и уводит первый атрибут со строки имени тега — признак считает
91
+ * имя тега, а маркер оказывается ниже. Дальше следующей строки маркер не достаёт: весь файл он
92
+ * не гасит, иначе один разрешённый случай прикрывал бы соседние.
90
93
  */
91
94
  function withoutMarked(text) {
92
- return text
93
- .split('\n')
94
- .filter((line) => !line.includes('native-ok'))
95
- .join('\n');
95
+ const lines = text.split('\n');
96
+
97
+ return lines.filter((line, index) => !line.includes('native-ok') && !lines[index - 1]?.includes('native-ok')).join('\n');
96
98
  }
97
99
 
98
100
  const allowlist = JSON.parse(readFileSync(join(ROOT, ALLOWLIST), 'utf8'));
@@ -31,12 +31,13 @@ import {
31
31
  REPO,
32
32
  STATUS_FIELD_ID,
33
33
  TASK_KEY,
34
- TOKEN_PATH,
35
34
  botToken,
35
+ describeTaskState,
36
36
  gh,
37
37
  ghJson,
38
38
  graphql,
39
39
  numberFromTitle,
40
+ taskState,
40
41
  } from './board.mjs';
41
42
 
42
43
  function parseArgs(argv) {
@@ -94,10 +95,13 @@ if (args.slug !== null && !/^[a-z0-9][a-z0-9-]*$/.test(args.slug)) {
94
95
  fail('slug — строчные латинские буквы, цифры и дефисы: имя ветки читают в списке из полусотни строк');
95
96
  }
96
97
 
97
- const token = botToken();
98
- if (!token) {
99
- fail(`нет токена бота (${TOKEN_PATH}): задача завелась бы от чужого имени`);
100
- }
98
+ /**
99
+ * Токен машинной записи необязателен: не назвавшее его дерево заводит задачу учётной записью,
100
+ * под которой залогинен клиент хостинга. Требование токена держало бы заведение задач у дерева,
101
+ * машинной записи не заводившего, и у дерева, чью запись ограничил хостинг, — а без заведения
102
+ * не начинается никакая работа вовсе. Кто именно завёл задачу, читается у неё самой.
103
+ */
104
+ const token = botToken() ?? undefined;
101
105
 
102
106
  let number = null;
103
107
  try {
@@ -194,5 +198,29 @@ adoptDraft();
194
198
 
195
199
  console.log(`[${TASK_KEY}-${number}] ${args.title}`);
196
200
  console.log(`https://github.com/${OWNER}/${REPO}/issues/${number}`);
201
+
202
+ /**
203
+ * Пятый шаг: заведение подтверждается ответом очереди работ, а не выводом этой команды.
204
+ *
205
+ * Все четыре шага выше отвечают за свои вызовы и молчат о том, видна ли задача тому, кто по
206
+ * ней придёт. Шестнадцать заведений подряд так и напечатали номер со ссылкой, не попав в
207
+ * очередь ни одно: учётная запись была ограничена хостингом, вызовы при этом отказа не дали.
208
+ */
209
+ let answer;
210
+ try {
211
+ answer = describeTaskState(number, taskState(number, { token }));
212
+ } catch (error) {
213
+ const reason = error instanceof OfflineError ? error.message : String(error.message ?? error);
214
+ answer = describeTaskState(number, { offline: reason });
215
+ }
216
+ for (const line of answer.lines) {
217
+ (answer.ok ? console.log : console.error)(`task-new: ${line}`);
218
+ }
219
+
197
220
  console.log(`\nВетка заводится отдельным вызовом:\n git checkout -b ${branch}`);
198
221
  console.log(`Взятая в работу задача переставляется на борде:\n npm run task:move -- ${number} ${IN_PROGRESS_STATUS}`);
222
+
223
+ if (!answer.ok) {
224
+ console.error(`task-new: проверь очередь работ целиком — npm run check:board`);
225
+ process.exit(1);
226
+ }
@@ -12,17 +12,22 @@ argument-hint: '[пусто | --days N]'
12
12
 
13
13
  ## 1. Собери, что пришло
14
14
 
15
+ Груз уезжает в приём, а не в очередь работ: сводка говорит о рабочих привычках команды, и в
16
+ открытой очереди это выложено всему свету. Записи приёма читает его админка; пока её нет,
17
+ собранное читается запросом к его хранилищу — как именно, сказано в компаньоне этой команды.
18
+
19
+ Записи, заведённые прежним порядком, лежат в очереди работ и никуда не делись:
20
+
15
21
  ```bash
16
- gh issue list --label agent-kit-feedback --state open --limit 100 \
22
+ gh issue list --label agent-kit-feedback --state all --limit 100 \
17
23
  --json number,title,body,createdAt
18
24
  ```
19
25
 
20
26
  Помощник хостинга и учётная запись машинной работы у каждого дерева свои — как их звать здесь,
21
27
  сказано в компаньоне правила `git-workflow`.
22
28
 
23
- Каждая запись заведена командой `agent-kit propose` из дерева, где пакет стоит. В теле ресурс,
24
- место, повод, готовый текст и сводка наблюдений того дерева. Имени дерева там нет намеренно:
25
- различать их можно только по сводке и по времени.
29
+ В каждой записи ресурс, место, повод и готовый текст. Признак дерева, приехавший с грузом,
30
+ адреса дерева не выдаёт: он считается хешем и по нему различают деревья, а не находят их.
26
31
 
27
32
  Своё дерево тоже потребитель — его наблюдения читаются прямо:
28
33
 
@@ -79,5 +84,5 @@ gh issue close <номер> --comment 'Вошло в #<номер задачи>.
79
84
  одна фраза о том, что менялось и почему. Журнал изменений при выпуске собирается из заголовков,
80
85
  и переписывать их задним числом — работа заново.
81
86
 
82
- **Выпуск отсюда не запускается.** Это отдельное решение владельца: слияние отчёта пакета не
87
+ **Выпуск отсюда не запускается.** Это отдельное решение владельца: слияние PR пакета не
83
88
  публикует. Скажи, что накопилось на выпуск, и остановись.
@@ -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'