@rt-tools/agent-kit 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +17 -0
  2. package/assets/checks/archive-age.mjs +107 -0
  3. package/assets/checks/archive-prune.mjs +46 -0
  4. package/assets/checks/check-archive-age.mjs +42 -0
  5. package/assets/checks/check-board.github.mjs +16 -0
  6. package/assets/checks/check-descriptions.mjs +123 -0
  7. package/assets/checks/check-dupes.mjs +31 -3
  8. package/assets/checks/check-file-size.mjs +47 -2
  9. package/assets/checks/check-turn-map.mjs +20 -3
  10. package/assets/checks/lib-common.mjs +12 -1
  11. package/assets/checks/lib-domains.mjs +1 -1
  12. package/assets/checks/rt-kit-checks.config.mjs +24 -1
  13. package/assets/checks/spec-anchors.mjs +18 -3
  14. package/assets/checks/spec-common.mjs +5 -1
  15. package/assets/defaults/project.sh +22 -17
  16. package/assets/defaults/turn-map.md +15 -19
  17. package/assets/docs/GLOSSARY.md +52 -58
  18. package/assets/hooks/rule-article.sh +12 -0
  19. package/assets/hooks/skill-gate.sh +5 -4
  20. package/assets/hooks/task-flow-guard.sh +20 -0
  21. package/assets/hooks/turn-exit-guard.sh +229 -4
  22. package/assets/laws/delivery.md +92 -104
  23. package/assets/laws/frontend-application.md +4 -0
  24. package/assets/laws/project-documentation.md +64 -68
  25. package/assets/laws/verifiability.md +32 -33
  26. package/assets/laws/work-conduct.md +167 -157
  27. package/assets/patterns/doc-style-sweep.md +1 -1
  28. package/assets/patterns/doc-style-trace.md +1 -1
  29. package/assets/patterns/git-workflow-commit.azure.md +1 -1
  30. package/assets/patterns/git-workflow-commit.github.md +7 -1
  31. package/assets/patterns/git-workflow-commit.gitlab.md +1 -1
  32. package/assets/patterns/git-workflow-docker.md +1 -1
  33. package/assets/patterns/git-workflow-merge.md +14 -3
  34. package/assets/patterns/git-workflow-pr.azure.md +1 -1
  35. package/assets/patterns/git-workflow-pr.github.md +1 -1
  36. package/assets/patterns/git-workflow-pr.gitlab.md +1 -1
  37. package/assets/patterns/git-workflow-restart.md +1 -1
  38. package/assets/patterns/git-workflow-secrets.md +1 -1
  39. package/assets/patterns/git-workflow-stack.md +93 -0
  40. package/assets/patterns/seo-page.md +1 -1
  41. package/assets/patterns/spec-driven-rule.md +55 -0
  42. package/assets/patterns/status-report-table.github.md +88 -0
  43. package/assets/patterns/task-flow-archive.md +3 -4
  44. package/assets/patterns/task-flow-close.md +6 -1
  45. package/assets/patterns/task-flow-start.md +17 -5
  46. package/assets/patterns/ts-procedure.md +1 -1
  47. package/assets/pitfalls/doc-style.md +5 -0
  48. package/assets/pitfalls/git-workflow.github.md +47 -0
  49. package/assets/pitfalls/task-flow.md +28 -0
  50. package/assets/pitfalls/testing.md +14 -0
  51. package/assets/pitfalls/turn-conduct.md +33 -0
  52. package/assets/rules/angular-patterns.md +1 -1
  53. package/assets/rules/api-layer.md +3 -3
  54. package/assets/rules/browser-verification.md +15 -1
  55. package/assets/rules/dependencies.md +1 -1
  56. package/assets/rules/deploy-flow.azure.md +1 -1
  57. package/assets/rules/deploy-flow.github.md +1 -1
  58. package/assets/rules/deploy-flow.gitlab.md +1 -1
  59. package/assets/rules/doc-style.md +18 -0
  60. package/assets/rules/entity-conventions.needs-admin.md +1 -1
  61. package/assets/rules/entity-models.md +1 -1
  62. package/assets/rules/git-workflow.azure.md +1 -1
  63. package/assets/rules/git-workflow.github.md +154 -181
  64. package/assets/rules/git-workflow.gitlab.md +1 -1
  65. package/assets/rules/lib-layers.md +1 -1
  66. package/assets/rules/observability.needs-app.md +1 -1
  67. package/assets/rules/platform-access.md +1 -1
  68. package/assets/rules/reuse-first.md +1 -1
  69. package/assets/rules/seo.md +4 -3
  70. package/assets/rules/shared-code.md +1 -1
  71. package/assets/rules/spec-driven.md +68 -1
  72. package/assets/rules/status-report.md +97 -0
  73. package/assets/rules/styling-bem.md +12 -0
  74. package/assets/rules/task-flow.md +102 -100
  75. package/assets/rules/testing.md +67 -66
  76. package/assets/rules/turn-conduct.md +146 -105
  77. package/assets/rules/turn-entry.md +7 -1
  78. package/assets/rules/typescript-conventions.md +1 -1
  79. package/assets/skills/agent-kit-extend.md +1 -1
  80. package/assets/skills/agent-kit.md +18 -1
  81. package/bin/agent-kit.d.ts.map +1 -1
  82. package/bin/agent-kit.js +25 -0
  83. package/bin/agent-kit.js.map +1 -1
  84. package/lib/cost.d.ts +44 -0
  85. package/lib/cost.d.ts.map +1 -0
  86. package/lib/cost.js +181 -0
  87. package/lib/cost.js.map +1 -0
  88. package/package.json +1 -1
  89. package/rt-tools-agent-kit-0.16.0.tgz +0 -0
  90. package/rt-tools-agent-kit-0.14.0.tgz +0 -0
@@ -36,6 +36,13 @@ const DEFAULTS = {
36
36
  docsDir: 'docs',
37
37
  /** Отложенное: про него проверки молчат — оно описывает прошлое, а не дерево. */
38
38
  archiveDir: 'docs/archive/',
39
+ /**
40
+ * Сколько суток живёт запись описания прошлого. `null` — срок не назначен, и тогда молчат
41
+ * обе стороны: сверка не краснеет, чистка не снимает. Умолчания у срока нет намеренно —
42
+ * пакет, назначивший его за дерево, начал бы сносить чужой архив в день установки, а
43
+ * снимается там разбор просьбы, которого нет больше нигде.
44
+ */
45
+ archiveRetentionDays: null,
39
46
  /**
40
47
  * Каталоги, чей указатель сверяется с содержимым: обзорный документ в них перечисляет
41
48
  * записи таблицей, и читатель ищет по ней, а не обходом. Пусто — сверки указателя нет.
@@ -80,6 +87,19 @@ const DEFAULTS = {
80
87
  * только это число.
81
88
  */
82
89
  proseSizeLimit: 300,
90
+ /**
91
+ * Предел веса текста слоя правил — в знаках. Строки меряют, сколько текста помещается на
92
+ * экран; веса они не меряют вовсе: правило о заявках занимает 282 строки при 13 595 знаках,
93
+ * а правило поставки — 272 строки при 21 508. Сжатие слоя срезает знаки и оставляет число
94
+ * переносов прежним, поэтому строковый предел достигнутого не закрепляет, и текст отрастает
95
+ * обратно молча.
96
+ *
97
+ * Число назначено по сжатому слою: самое тяжёлое правило весит 21 508 знаков, и предел стоит
98
+ * чуть выше него. Ниже ставить нельзя — это потребовало бы резать заново то, что уже прошло
99
+ * сжатие; выше незачем — тогда он ничего не закрепляет. Ноль выключает проверку веса вовсе:
100
+ * дерево, не назвавшее числа, судится по-прежнему одними строками.
101
+ */
102
+ proseCharLimit: 22000,
83
103
  /**
84
104
  * Корни, под которыми лежит текст слоя правил, и его источники. Файл отсюда судится
85
105
  * пределом текста, всё остальное — пределом кода. Пусто — предел один на всё дерево.
@@ -258,7 +278,10 @@ export const parseAllowlist = (name, sides = ['accepted', 'debt']) => {
258
278
  return new Map();
259
279
  }
260
280
  if (Array.isArray(entries) || typeof entries !== 'object' || entries === null) {
261
- refuse(`«${side}» записан не объектом — у записи нет места ни для причины, ни для номера задачи`);
281
+ refuse(
282
+ `«${side}» записан не объектом — у записи нет места ни для причины, ни для номера задачи. ` +
283
+ `Форма: {"${side}": {"<ключ>": {"reason": "<почему>", "task": "${key || 'КЛЮЧ'}-<номер>"}}}`,
284
+ );
262
285
  }
263
286
  const parsed = new Map();
264
287
  for (const [entry, value] of Object.entries(entries)) {
@@ -108,7 +108,11 @@ function checkRuleImplementation(specFile, text, mapFile, heading = '## Прав
108
108
 
109
109
  const rows = new Map();
110
110
  for (const line of rowsOfMap(specFile, mapFile, mapHeading)) {
111
- const cells = line.match(/^\|([^|]+)\|([^|]*)\|\s*$/);
111
+ // Привязка записывается двумя формами, и читаются обе. Таблица — прежняя; список — та,
112
+ // ради которой из компаньонов уходят пробелы выравнивания: форматтер добивает столбцы до
113
+ // общей ширины, и в компаньонах дерева это 120 017 знаков из 328 738, то есть 37%.
114
+ // Связь при этом не меняется: она идёт по тексту утверждения, а не по форме строки.
115
+ const cells = line.match(/^\|([^|]+)\|([^|]*)\|\s*$/) ?? line.match(/^-\s+\*\*(.+?)\*\*\s+—\s+(.*)$/);
112
116
  if (!cells) {
113
117
  continue;
114
118
  }
@@ -210,8 +214,14 @@ function symbolOwners() {
210
214
 
211
215
  for (const file of SOURCE_ROOTS.flatMap((root) => walk(root, (name) => name.endsWith('.ts') || name.endsWith('.html')))) {
212
216
  const text = file.endsWith('.ts') ? codeOf(read(file)) : read(file);
213
- for (const [token] of text.matchAll(/[A-Za-z_][\w-]*/g)) {
217
+ // Решётка входит в токен: приватное поле класса объявлено с ней, и якорь на него иначе
218
+ // не попадал бы в перечень владельцев ни разу. Имя без решётки помнится наравне с ним
219
+ // самим — привязки прежней формы остаются зелёными, и переходить разом не приходится.
220
+ for (const [token] of text.matchAll(/#?[A-Za-z_][\w-]*/g)) {
214
221
  remember(token, file);
222
+ if (token.startsWith('#')) {
223
+ remember(token.slice(1), file);
224
+ }
215
225
  if (token.includes('-')) {
216
226
  token.split('-').forEach((part) => part && remember(part, file));
217
227
  }
@@ -242,7 +252,12 @@ function checkTracedAnchors() {
242
252
 
243
253
  const owners = symbolOwners();
244
254
  for (const { mapFile, path, symbol } of declared) {
245
- const here = (codeAt(path).match(new RegExp(`\\b${escapeForRegExp(symbol)}\\b`, 'g')) || []).length;
255
+ // Граница слова ставится только там, где она есть: перед решёткой её нет, и образец с
256
+ // ней давал бы ноль вхождений у всякого приватного имени.
257
+ const bound = symbol.startsWith('#')
258
+ ? `${escapeForRegExp(symbol)}\\b`
259
+ : `\\b${escapeForRegExp(symbol)}\\b`;
260
+ const here = (codeAt(path).match(new RegExp(bound, 'g')) || []).length;
246
261
  const elsewhere = [...(owners.get(symbol) || [])].filter((file) => file !== path).length;
247
262
  if (here + elsewhere < 2) {
248
263
  report(
@@ -66,8 +66,12 @@ const E2E_ROOTS = CONFIG.e2eRoots;
66
66
  * переписан целиком. Алфавит не перечисляется диапазонами: перечисленные молча не покрывают
67
67
  * соседнего, и промах выглядит отсутствием привязки. Путь при этом остаётся латинским — он
68
68
  * адрес в дереве, а не слово текста.
69
+ *
70
+ * Решётка перед именем законна: приватное поле класса объявлено с ней, и записанное без неё имя
71
+ * называет метод не тем именем, каким он объявлен. Проверка при этом остаётся зелёной — граница
72
+ * слова перед решёткой есть, — поэтому промах не краснеет ни разу и виден только чтением.
69
73
  */
70
- const ANCHOR = /`([\w./-]+\.[A-Za-z]{2,10}):(\p{L}[\p{L}\p{N}_-]*|_[\w-]*)`/gu;
74
+ const ANCHOR = /`([\w./-]+\.[A-Za-z]{2,10}):(#?\p{L}[\p{L}\p{N}_-]*|#?_[\w-]*)`/gu;
71
75
  /**
72
76
  * Явный вердикт вместо якоря: статья, которой в дереве исполняться негде. Так бывает
73
77
  * законно — правило говорит о службе, которой дерево не держит, или о движении человека,
@@ -88,7 +88,7 @@ rt_push_checks_default() {
88
88
 
89
89
  for check in check-doc-paths check-specs check-file-size check-dupes check-styles \
90
90
  check-lib-layers check-reuse check-schema-drift check-states check-state-next \
91
- check-turn-map check-push-gate; do
91
+ check-turn-map check-archive-age check-push-gate; do
92
92
  [ -f "$root/$RT_CHECKS_DIR/$check.mjs" ] && printf '%s\n' "node $RT_CHECKS_DIR/$check.mjs"
93
93
  done
94
94
 
@@ -258,24 +258,29 @@ rt_shell_writes_default() {
258
258
  # Судится заголовок команды — именно в нём стоит тот путь, куда команда пишет.
259
259
  #
260
260
  # Исключение — интерпретатор: ему код приходит телом, и путь записи стоит именно там. Признак
261
- # ошибается в сторону лишнего чтения тела: имя интерпретатора, стоящее в команде где угодно,
262
- # возвращает разбор тела целиком.
261
+ # читается у той строки, которая тело открыла, а не у всей команды: тело принадлежит команде
262
+ # своего заголовка. Прежде он читался у всего текста разом, и слово из документа отключало
263
+ # вырезание целиком — строка «**Чем проверяется:** `bash projects/…`» в замысле делала запись
264
+ # `plan.md` правкой кода приложения. Гард отбивал тем самым запись того файла, отсутствием
265
+ # которого он же и отказывает.
263
266
  rt_shell_paths_default() {
264
267
  text="$(printf '%s' "$1" | tr "\"'\`" ' ')"
265
- if ! printf '%s' "$text" \
266
- | grep -Eq '(^|[|;&(]|[[:space:]])(python3?|node|ruby|perl|php|deno|bun|bash|sh|zsh)([[:space:]]|$)'; then
267
- text="$(printf '%s' "$text" | awk '
268
- function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
269
- tag != "" { if (trim($0) == tag) tag = ""; next }
270
- {
271
- print
272
- if (match($0, /<<-?[ \t]*[A-Za-z_][A-Za-z0-9_]*/)) {
273
- t = substr($0, RSTART, RLENGTH)
274
- sub(/^<<-?[ \t]*/, "", t)
275
- tag = t
276
- }
277
- }')"
278
- fi
268
+ text="$(printf '%s' "$text" | awk '
269
+ function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
270
+ tag != "" {
271
+ if (keep) { print }
272
+ if (trim($0) == tag) { tag = ""; keep = 0 }
273
+ next
274
+ }
275
+ {
276
+ print
277
+ if (match($0, /<<-?[ \t]*[A-Za-z_][A-Za-z0-9_]*/)) {
278
+ t = substr($0, RSTART, RLENGTH)
279
+ sub(/^<<-?[ \t]*/, "", t)
280
+ tag = t
281
+ keep = ($0 ~ /(^|[|;&(]|[ \t])(python3?|node|ruby|perl|php|deno|bun|bash|sh|zsh)([ \t]|$)/)
282
+ }
283
+ }')"
279
284
 
280
285
  # Пути берутся только у тех кусков команды, которые пишут. Прежде брались у всей строки
281
286
  # целиком, и команда чтения, сцепленная с записью, отдавала свои пути как цели записи:
@@ -9,19 +9,17 @@
9
9
 
10
10
  ## Состояния и обязательные действия
11
11
 
12
- | Состояние | Обязательное действие | Ведёт паттерн |
13
- | ------------------------- | ------------------------------------------------------ | ------------------ |
14
- | `просьба-не-разобрана` | разведка по дереву, затем вопросы | `task-flow-start` |
15
- | `разбор-закрыт` | договорённость о продукте либо причина её отсутствия | `task-flow-start` |
16
- | `договорённость-записана` | завести задачу, ветку и папку | `task-flow-start` |
17
- | `задача-взята` | написать замысел | `task-flow-start` |
18
- | `замысел-записан` | делать первый этап | `task-flow-start` |
19
- | `этап-идёт` | доделать этап и отметить в ходе работы | `task-flow-resume` |
20
- | `этапы-кончились` | прогнать набор и открыть PR черновиком | `task-flow-close` |
21
- | `работа-отдана` | взять следующую задачу | `task-flow-resume` |
22
- | `разбор-кончился` | влить договорённость, привести тексты, разобрать папку | `task-flow-close` |
23
- | `папка-разобрана` | снять черновик и попросить влить | `task-flow-archive` |
24
- | `влито` | разбор работы правилами и сверка очереди | `task-flow-archive` |
12
+ - `просьба-не-разобрана` разведка по дереву, затем вопросы; ведёт `task-flow-start`
13
+ - `разбор-закрыт` договорённость о продукте либо причина её отсутствия; ведёт `task-flow-start`
14
+ - `договорённость-записана` завести задачу, ветку и папку; ведёт `task-flow-start`
15
+ - `задача-взята` написать замысел; ведёт `task-flow-start`
16
+ - `замысел-записан` делать первый этап; ведёт `task-flow-start`
17
+ - `этап-идёт` доделать этап и отметить в ходе работы; ведёт `task-flow-resume`
18
+ - `этапы-кончились` прогнать набор и открыть PR черновиком; ведёт `task-flow-close`
19
+ - `работа-отдана` взять следующую задачу; ведёт `task-flow-resume`
20
+ - `разбор-кончился` влить договорённость, привести тексты, разобрать папку; ведёт `task-flow-close`
21
+ - `папка-разобрана` снять черновик и попросить влить; ведёт `task-flow-archive`
22
+ - `влито` разбор работы правилами и сверка очереди; ведёт `task-flow-archive`
25
23
 
26
24
  Ни у одного состояния обязательное действие не звучит как «ждать». Прогон, разбор владельцем и
27
25
  слияние идут без исполнителя и от взгляда быстрее не становятся.
@@ -30,12 +28,10 @@
30
28
 
31
29
  Способов четыре, и других нет.
32
30
 
33
- | Выход | Чем подтверждается |
34
- | -------------------------------------------------- | --------------------------------------------------------------- |
35
- | вопрос владельцу, ответа на который в правилах нет | вопрос задан, и за тот же ход правила читались |
36
- | отказ гарда | отказ назван владельцу, обход не искался |
37
- | заполненное окно там, где сжатия нет | ход работы дописан, передача написана |
38
- | работа отдана, и следующая начата | PR открыт, и по следующей задаче сделано действие, а не сказано |
31
+ - **вопрос владельцу, ответа на который в правилах нет** — вопрос задан, и за тот же ход правила читались
32
+ - **отказ гарда** отказ назван владельцу, обход не искался
33
+ - **заполненное окно там, где сжатия нет** ход работы дописан, передача написана
34
+ - **работа отдана, и следующая начата** PR открыт, и по следующей задаче сделано действие, а не сказано
39
35
 
40
36
  Там, где дерево объявило порог сжатия ниже порога остановки, заполненное окно ход не кончает:
41
37
  контекст сжимается, передача приходит входом, и работа идёт дальше тем же заходом. Порог
@@ -13,70 +13,64 @@
13
13
 
14
14
  ## Слой правил
15
15
 
16
- | Термин | Что это |
17
- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
- | Закон | Файл в каталоге конституции. Говорит, что должно быть верно, и не знает ни путей, ни имён файлов. Верен для любого приложения этого класса |
19
- | Законы приложения | Слой законов, верных только для этого приложения: деньги, локали, доступ. Предметность в них законна — она их предмет |
20
- | Правило | Скил с `kind: rule`. Привязывает закон к этому дереву: чем это здесь названо и где лежит |
21
- | Паттерн | Скил с `kind: pattern`. Готовый код и порядок действий; стоит при правиле |
22
- | Компаньон | Файл `implementation.md` рядом с правилом: имена и пути этого дерева. Пакет знает приём, но не знает имён — их пишет проект |
23
- | Спек | Описание домена: как он работает. Говорит об установившемся, а не о предстоящем |
24
- | Домен | Предмет, у которого свой спек. Выросший домен делится на поддомены, а не на соседние домены |
25
- | Сценарий | Наблюдаемое поведение под номером `SC-<ПРЕФИКС>-<НОМЕР>`. Номер стоит в заголовке теста |
26
- | Привязка | Строка `` `файл:символ` `` в компаньоне или в спутнике спека место, где утверждение исполняется |
27
- | Спутник | Файл рядом со спеком или правилом: компаньон, перечень сценариев |
28
- | Договорённость о продукте | Как продукт себя поведёт, записанное до кода. Единственное место, где спек говорит о будущем; после выкатки вливается в спек домена, а директория удаляется |
29
- | Ресурс | Единица того, что везёт пакет правил: закон, правило, паттерн, гард, проверка, роль, команда, конвейер, шаблон, умолчание, документ |
30
- | Раскладка | Перенос ресурса из пакета в дерево по его роду и настройке слоя |
31
- | Разложенный файл | Файл в дереве с шапкой пакета. Правится не на месте, а надстройкой: правка на месте теряется на следующей раскладке |
32
- | Надстройка | Файл дерева, который сливается с разложенным по заголовкам разделов |
33
- | Выключенная роль | Роль, вызов которой дерево перестало считать обязательным: гард при ней молчит. Названа списком в настройке дерева, из раскладки не убирается и зовётся руками |
34
- | Наблюдение | Строка о событии слоя правил: правило загружено, гейт отбил, гард отказал. Пишет гард, живёт в дереве, наружу уезжает счётчиками. Журналом не называется |
35
- | Сводка | Что наблюдения говорят за отрезок дней: чем пользовались, чем ни разу, обо что спотыкались |
36
- | Предложение | Готовая формулировка правки правил с адресом: пакет, компаньон или дерево. Приносит разбор закрытой задачи или слово посреди работы, отправляет человек командой |
37
- | Слово | Реплика человека посреди работы о слое правил: что мешает, чего не хватило, что сработало не так. Ложится блоком в файл предложений, а не живёт до конца захода |
16
+ - **Закон** файл в каталоге конституции: что должно быть верно, без путей и имён файлов. Верен для любого приложения этого класса
17
+ - **Законы приложения** слой законов, верных только для этого приложения: деньги, локали, доступ. Предметность в них законна — она их предмет
18
+ - **Правило** скил с `kind: rule`. Привязывает закон к этому дереву: чем это здесь названо и где лежит
19
+ - **Паттерн** скил с `kind: pattern`. Готовый код и порядок действий; стоит при правиле
20
+ - **Компаньон** файл `implementation.md` рядом с правилом: имена и пути этого дерева. Пакет знает приём, но не знает имён — их пишет проект
21
+ - **Спек** описание домена: как он работает. Говорит об установившемся, а не о предстоящем
22
+ - **Домен** предмет, у которого свой спек. Выросший домен делится на поддомены, а не на соседние домены
23
+ - **Сценарий** наблюдаемое поведение под номером `SC-<ПРЕФИКС>-<НОМЕР>`. Номер стоит в заголовке теста
24
+ - **Привязка** строка `` `файл:символ` `` в компаньоне или в спутнике спека место, где утверждение исполняется
25
+ - **Спутник** файл рядом со спеком или правилом: компаньон, перечень сценариев
26
+ - **Договорённость о продукте** как продукт себя поведёт, записанное до кода: единственное место, где спек говорит о будущем. После выкатки вливается в спек домена
27
+ - **Ресурс** единица того, что везёт пакет правил: закон, правило, паттерн, гард, проверка, роль, команда, конвейер, шаблон, умолчание, документ
28
+ - **Раскладка** перенос ресурса из пакета в дерево по его роду и настройке слоя
29
+ - **Разложенный файл** файл в дереве с шапкой пакета. Правится не на месте, а надстройкой: правка на месте теряется на следующей раскладке
30
+ - **Надстройка** файл дерева, который сливается с разложенным по заголовкам разделов
31
+ - **Выключенная роль** роль, вызов которой дерево перестало считать обязательным: гард при ней молчит. Названа списком в настройке дерева, из раскладки не убирается и зовётся руками
32
+ - **Наблюдение** строка о событии слоя правил: правило загружено, гейт отбил, гард отказал. Пишет гард, живёт в дереве, наружу уезжает счётчиками. Журналом не называется
33
+ - **Сводка** что наблюдения говорят за отрезок дней: чем пользовались, чем ни разу, обо что спотыкались
34
+ - **Предложение** готовая формулировка правки правил с адресом: пакет, компаньон или дерево. Отправляет её человек командой
35
+ - **Слово** реплика человека посреди работы о слое правил: что мешает, чего не хватило, что сработало не так. Ложится блоком в файл предложений, а не живёт до конца захода
38
36
 
39
37
  ## Работа
40
38
 
41
- | Термин | Что это |
42
- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43
- | Задача | Единица работы в очереди работ. Заводится до ветки, и номер её стоит в имени ветки и в заголовке PR |
44
- | PR | Заявка на слияние: то же название, что у задачи, переведённое в сделанное. Отчётом, пул-реквестом и мёрдж-реквестом не называется ни в файлах, ни в разговоре |
45
- | Очередь работ | Доска, на которой видно состояние каждой задачи. Ветки она не видит |
46
- | Папка задачи | Одна работа от разбора до слияния: разбор просьбы, замысел, ход работы. Умирает со слияниемразбирается, и объясняющее решение уезжает в архив |
47
- | Разбор | Расспрос владельца до первой правки. Записывается его словами и задним числом не переписывается |
48
- | Замысел | Файл папки задачи: след задачи и этапы с признаками готовности. После написания не правится с ним сверяют результат при приёмке |
49
- | Ход работы | Файл папки задачи: «Где стоим», решения по ходу с причинами, записи заходов. Единственное место, где отмечается сделанное. Журналом не называется |
50
- | Состояние работы | Единица, которой работа ведётся: у каждого состояния названы вход, обязательное действие и выход. Объявляется машиночитаемой строкой в разделе «Где стоим» хода работы — гард судит объявленный переход, а не наличие файлов |
51
- | След задачи | Раздел замысла: какие спеки, законы, правила и части кода работа задевает |
52
- | Заход | Одна сессия работы над задачей. Работа живёт дольше одного захода, и между ними её состояние держит только ход работы |
53
- | Заполнение окна | Доля места захода, которую он уже занял: вход, запись в кэш, прочитанное из кэша и вывод последнего ответа, делённые на размер окна. Не «расход» и не «бюджет»: речь о месте, а не о деньгах |
54
- | Передача | Текст, которым заход закрывается: рабочее дерево, ветка, задача, где лежит ход работы, что сделано, следующий шаг, особенности захода. Кладётся вне дерева и не коммитится |
55
- | Эпик | Серия задач одной темы, выполняемых в назначенном порядке. Живёт в двух местах сразу: карточка в очереди работ с меткой эпика и замысел рядом с ней — что за возможность разрабатывается, какие задачи входят и в каком порядке. Шире одной ветки. Линией работ не называется |
56
- | Архив | Записи о состоявшемся: что объясняет закрытое решение. После выкатки не правится |
39
+ - **Задача** единица работы в очереди работ. Заводится до ветки, и номер её стоит в имени ветки и в заголовке PR
40
+ - **PR** заявка на слияние: то же название, что у задачи, переведённое в сделанное. Отчётом, пул-реквестом и мёрдж-реквестом не называется — ни в файлах, ни в разговоре
41
+ - **Очередь работ** доска, на которой видно состояние каждой задачи. Ветки она не видит
42
+ - **Папка задачи** одна работа от разбора до слияния: разбор просьбы, замысел, ход работы. Умирает со слиянием разбирается, и объясняющее решение уезжает в архив
43
+ - **Разбор** расспрос владельца до первой правки. Записывается его словами и задним числом не переписывается
44
+ - **Замысел** файл папки задачи: след задачи и этапы с признаками готовности. После написания не правитсяс ним сверяют результат при приёмке
45
+ - **Ход работы** файл папки задачи: «Где стоим», решения по ходу с причинами, записи заходов. Единственное место, где отмечается сделанное. Журналом не называется
46
+ - **Состояние работы** единица, которой работа ведётся: у каждого названы вход, обязательное действие и выход. Объявляется строкой в разделе «Где стоим» хода работы, и гард судит её, а не наличие файлов
47
+ - **След задачи** раздел замысла: какие спеки, законы, правила и части кода работа задевает
48
+ - **Заход** одна сессия работы над задачей. Работа живёт дольше захода, и между ними её состояние держит ход работы
49
+ - **Заполнение окна** доля места захода, которую он уже занял: вход, запись в кэш, прочитанное из кэша и вывод последнего ответа, делённые на размер окна. Не «расход» и не «бюджет»: речь о месте, а не о деньгах
50
+ - **Передача** текст, которым заход закрывается: рабочее дерево, ветка, задача, где лежит ход работы, что сделано, следующий шаг, особенности захода. Кладётся вне дерева и не коммитится
51
+ - **Эпик** серия задач одной темы в назначенном порядке, шире одной ветки. Живёт в двух местах: карточка с меткой эпика и замысел рядом с ней. Линией работ не называется
52
+ - **Архив** записи о состоявшемся: что объясняет закрытое решение. После выкатки не правится
57
53
 
58
54
  ## Проверки
59
55
 
60
- | Термин | Что это |
61
- | --------------------- | --------------------------------------------------------------------------------------------------------------------------- |
62
- | Гард | Хук агента, который отбивает действие до того, как оно сделано, и говорит, чем отказ снимается |
63
- | Гейт | Требование, которое пропускает действие один раз за сессию после того, как выполнено: загружено правило, пройдены проверки |
64
- | Отказ в пользу работы | Устройство гарда, при котором любая его поломка пропускает действие. Сломанный гард не имеет права остановить работу совсем |
65
- | Прогон | Запуск набора сценариев. «Тесты гоняются», а не «запускаются в работу» |
66
- | Сверка | Проверка, которая ничего не правит, а называет расхождения: раскладки с пакетом, спеков с кодом, очереди работ с ветками |
67
- | Замер | Число, снятое с работающего приложения. Взгляд на экран замером не является |
56
+ - **Гард** хук агента, который отбивает действие до того, как оно сделано, и говорит, чем отказ снимается
57
+ - **Гейт** требование, которое пропускает действие один раз за сессию после того, как выполнено: загружено правило, пройдены проверки
58
+ - **Отказ в пользу работы** устройство гарда, при котором любая его поломка пропускает действие. Сломанный гард не имеет права остановить работу совсем
59
+ - **Прогон** запуск набора сценариев. «Тесты гоняются», а не «запускаются в работу»
60
+ - **Сверка** проверка, которая ничего не правит, а называет расхождения: раскладки с пакетом, спеков с кодом, очереди работ с ветками
61
+ - **Замер** число, снятое с работающего приложения. Взгляд на экран замером не является
68
62
 
69
63
  ## Так не пишем
70
64
 
71
- | Так не пишем | Пишем так |
72
- | --------------------------- | ---------------------------------------------------------------------------------------------- |
73
- | спека (о тесте) | тест — файл рядом с исходником; спек — документ. Одна буква разницы, а значения противоположны |
74
- | таска, тикет | задача |
75
- | пул-реквест, мёрдж-реквест | PR, а действие — слияние |
76
- | отчёт (о заявке на слияние) | PR; отчёт — сводка данных, и слово занято ею |
77
- | джоба, пайплайн | конвейер и его шаг |
78
- | хендофф | передача |
79
- | бэклог | очередь работ |
80
- | линия работ | эпик |
81
- | контекст-виндоу | окно захода, а его доля — заполнение окна |
82
- | скилл, скилы | правило, паттерн или скил без закона — по тому, что это на самом деле |
65
+ Слева то, что не пишется и не произносится нигде; справа — чем это зовут здесь.
66
+
67
+ - **спека (о тесте)** тест — файл рядом с исходником; спек — документ. Одна буква разницы, а значения противоположны
68
+ - **таска, тикет** задача
69
+ - **пул-реквест, мёрдж-реквест** PR, а действие — слияние
70
+ - **отчёт (о заявке на слияние)** PR; отчёт — сводка данных, и слово занято ею
71
+ - **джоба, пайплайн** конвейер и его шаг
72
+ - **хендофф** передача
73
+ - **бэклог** очередь работ
74
+ - **линия работ** эпик
75
+ - **контекст-виндоу** окно захода, а его доля — заполнение окна
76
+ - **скилл, скилы** правило, паттерн или скил без закона — по тому, что это на самом деле
@@ -75,6 +75,18 @@ rt_rule_article_marks() {
75
75
  # Текст статьи, внутри которой стоит строка с признаком. Статья начинается ближайшим сверху
76
76
  # пунктом списка верхнего уровня и кончается перед следующим таким пунктом или перед строкой без
77
77
  # отступа: продолжение статьи всегда идёт с отступом.
78
+ # Заголовок статьи — её жирная первая фраза. Отказ гейта называет статьи ею, а не пересказывает
79
+ # их телом: правило заход грузит следом, и тело придёт в контекст вторым разом. У правила текстов
80
+ # отказ печатал 4 134 знака одиннадцатью статьями, их заголовки — 718.
81
+ #
82
+ # Заголовок при этом не украшение: по нему идёт привязка утверждения к коду, и он же называет
83
+ # статью в отказе так, что её видно в правиле глазами.
84
+ rt_rule_article_heads() {
85
+ rule_file="$1"
86
+ edited="$2"
87
+ rt_rule_articles "$rule_file" "$edited" 2>/dev/null | grep -o '^- \*\*[^*]*\*\*' 2>/dev/null
88
+ }
89
+
78
90
  rt_rule_article_at() {
79
91
  awk -v mark="$2" '
80
92
  NR <= mark && /^- / { start = NR }
@@ -186,13 +186,14 @@ fi
186
186
  # Правило целиком остаётся вторым ходом — для того, кому статьи мало.
187
187
  # shellcheck disable=SC1090
188
188
  [ -f "$rt_hooks_dir/rule-article.sh" ] && . "$rt_hooks_dir/rule-article.sh" 2>/dev/null
189
- if [ "$kind" != "command" ] && [ "$kind" != "browser" ] && command -v rt_rule_articles >/dev/null 2>&1; then
190
- article="$(rt_rule_articles "$root/$rules_dir/${req}/SKILL.md" "$target" 2>/dev/null)"
189
+ if [ "$kind" != "command" ] && [ "$kind" != "browser" ] && command -v rt_rule_article_heads >/dev/null 2>&1; then
190
+ article="$(rt_rule_article_heads "$root/$rules_dir/${req}/SKILL.md" "$target" 2>/dev/null)"
191
191
  if [ -n "$article" ]; then
192
- reason="Отбито гейтом правил. Под эту правку подпадает статья правила «${req}»:
192
+ reason="Отбито гейтом правил. Под эту правку подпадают статьи правила «${req}»:
193
193
 
194
194
  ${article}
195
- Статья снимает чтение правила целиком, а не отказ: загрузи правило «${req}» инструментом Skill и повтори действие. ${fallback} Для этой области это происходит один раз за сессию."
195
+
196
+ Текст этих статей придёт в контекст вместе с правилом, и пересказывать его здесь значило бы платить за один текст дважды: загрузи правило «${req}» инструментом Skill и повтори действие. ${fallback} Для этой области это происходит один раз за сессию."
196
197
  fi
197
198
  fi
198
199
 
@@ -215,6 +215,26 @@ case "$state" in
215
215
  ;;
216
216
  esac
217
217
 
218
+ # Папка задачи едет в ветку коммитом, а не живёт в одном рабочем дереве. Четыре требования выше
219
+ # смотрят диск, и папка, ни разу не закоммиченная, проходит их все без единого отказа — а
220
+ # признак отданной работы гард берёт из истории, и там её нет. Отказ приходит в последней точке,
221
+ # на открытии заявки, когда папка уже разобрана своими руками: чинить нечего, замысел снят, и
222
+ # собирать его приходится заново по памяти. За одну задачу это стоило восьми вызовов и двух
223
+ # отказов подряд.
224
+ #
225
+ # Второе следствие тише: ход работы, живущий в рабочем дереве, не виден никому. Владелец видит
226
+ # ветку без единого следа того, что в ней делается, а следующий заход — пустоту вместо «Где
227
+ # стоим», если рабочее дерево между заходами сменилось.
228
+ #
229
+ # Спрашивается та же история, что и у признака отданной работы: папка стоит в `HEAD` либо её
230
+ # добавлял коммит ветки. Нет git — требования нет: спросить историю нечем.
231
+ if [ -n "$(git rev-parse --verify HEAD 2>/dev/null)" ]; then
232
+ in_tree="$(git ls-tree -d --name-only HEAD -- "$tasks_dir/$branch" 2>/dev/null | head -1)"
233
+ if [ -z "$in_tree" ]; then
234
+ deny "BLOCKED by task-flow: папка задачи '${tasks_dir}/${branch}' лежит в рабочем дереве, а в историю ветки не заведена. Заведи её коммитом (git add ${tasks_dir}/${branch} && git commit), затем повтори: признак отданной работы гард берёт из истории, и с некоммиченной папкой отказ придёт на открытии заявки — когда папка уже разобрана и чинить нечего. Правило — скил task-flow."
235
+ fi
236
+ fi
237
+
218
238
  # Строка обхода: поведение не меняется, договорённость о продукте не нужна. Причина обязана
219
239
  # стоять — без неё обход становится умолчанием.
220
240
  if grep -qE '^\*\*Поведение:\*\*[[:space:]]*не меняется[[:space:]]*—[[:space:]]*\S' "$plan" 2>/dev/null; then