@rt-tools/agent-kit 0.3.0 → 0.5.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 (202) hide show
  1. package/README.md +194 -30
  2. package/assets/agents/business-analyst.md +74 -0
  3. package/assets/agents/project-manager.md +70 -0
  4. package/assets/agents/qa-engineer.md +72 -0
  5. package/assets/agents/skill-curator.md +110 -0
  6. package/assets/agents/spec-critic.md +44 -0
  7. package/assets/agents/spec-writer.md +50 -0
  8. package/assets/checks/board.github.mjs +329 -0
  9. package/assets/checks/check-board.github.mjs +181 -0
  10. package/assets/checks/check-doc-paths.mjs +163 -0
  11. package/assets/checks/check-dupes.mjs +277 -0
  12. package/assets/checks/check-lib-layers.mjs +573 -0
  13. package/assets/checks/check-reuse.mjs +208 -0
  14. package/assets/checks/check-schema-drift.mjs +186 -0
  15. package/assets/checks/check-specs.mjs +1086 -0
  16. package/assets/checks/check-styles.mjs +109 -0
  17. package/assets/checks/rt-kit-checks.config.mjs +134 -0
  18. package/assets/checks/task-new.github.mjs +198 -0
  19. package/assets/commands/skill-curator.md +70 -0
  20. package/assets/defaults/gate-map.sh +106 -0
  21. package/assets/defaults/project.sh +204 -0
  22. package/assets/hooks/browser-device-id.sh +0 -0
  23. package/assets/hooks/browser-guard-device-id.sh +2 -1
  24. package/assets/hooks/browser-guard-no-asking.sh +27 -0
  25. package/assets/hooks/browser-guard-no-listing.sh +2 -1
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
  27. package/assets/hooks/browser-guard-require-select.sh +2 -1
  28. package/assets/hooks/commit-msg.sh +1 -1
  29. package/assets/hooks/constitution-index.sh +5 -4
  30. package/assets/hooks/dev-server-guard.sh +8 -6
  31. package/assets/hooks/docs-guard.sh +223 -37
  32. package/assets/hooks/git-guard-delivery.sh +171 -31
  33. package/assets/hooks/git-guard-main.sh +1 -0
  34. package/assets/hooks/git-guard-push-tests.sh +34 -13
  35. package/assets/hooks/glossary-load.sh +23 -0
  36. package/assets/hooks/grill-gate.sh +96 -0
  37. package/assets/hooks/lint-after-edit.sh +155 -30
  38. package/assets/hooks/qa-dataid-guard.sh +72 -32
  39. package/assets/hooks/reuse-first-guard.sh +105 -34
  40. package/assets/hooks/skill-gate-rearm.sh +1 -0
  41. package/assets/hooks/skill-gate.sh +75 -15
  42. package/assets/hooks/skill-loaded.sh +1 -0
  43. package/assets/hooks/sql-guard.sh +606 -56
  44. package/assets/hooks/task-context-load.sh +100 -0
  45. package/assets/hooks/task-flow-guard.sh +118 -0
  46. package/assets/laws/{access.md → application/access.md} +1 -4
  47. package/assets/laws/{locales.md → application/locales.md} +1 -3
  48. package/assets/laws/application/money.md +41 -0
  49. package/assets/laws/application/ownership.md +32 -0
  50. package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
  51. package/assets/laws/code-structure.md +7 -6
  52. package/assets/laws/delivery.md +53 -3
  53. package/assets/laws/entity-editing.md +49 -55
  54. package/assets/laws/entity-models.md +4 -14
  55. package/assets/laws/frontend-application.md +5 -5
  56. package/assets/laws/lib-imports.md +14 -1
  57. package/assets/laws/lists.md +33 -0
  58. package/assets/laws/navigation.md +40 -0
  59. package/assets/laws/project-documentation.md +27 -8
  60. package/assets/laws/reuse-first.md +26 -21
  61. package/assets/laws/shared-code.md +13 -1
  62. package/assets/laws/verifiability.md +30 -1
  63. package/assets/laws/work-conduct.md +59 -0
  64. package/assets/patterns/admin-lists-screen.md +131 -0
  65. package/assets/patterns/admin-nav-item.md +71 -0
  66. package/assets/patterns/angular-patterns-state.md +29 -22
  67. package/assets/patterns/api-layer-pair.md +40 -30
  68. package/assets/patterns/browser-verification-measure.md +41 -38
  69. package/assets/patterns/browser-verification-stand.md +106 -42
  70. package/assets/patterns/component-structure-new.md +33 -32
  71. package/assets/patterns/dependencies-upgrade.md +65 -0
  72. package/assets/patterns/doc-style-sweep.md +65 -28
  73. package/assets/patterns/doc-style-write.md +36 -33
  74. package/assets/patterns/entity-aside.md +136 -0
  75. package/assets/patterns/entity-models-new.md +124 -0
  76. package/assets/patterns/entity-store.md +91 -0
  77. package/assets/patterns/git-workflow-commit.azure.md +259 -0
  78. package/assets/patterns/git-workflow-commit.github.md +337 -0
  79. package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
  80. package/assets/patterns/git-workflow-merge.md +42 -25
  81. package/assets/patterns/git-workflow-migration.md +61 -31
  82. package/assets/patterns/git-workflow-restart.md +20 -20
  83. package/assets/patterns/lib-layers-move.md +50 -32
  84. package/assets/patterns/lib-layers-new.md +41 -29
  85. package/assets/patterns/ownership-scope-resolve.md +69 -0
  86. package/assets/patterns/permissions-procedure.md +35 -33
  87. package/assets/patterns/platform-access-di.md +39 -25
  88. package/assets/patterns/pricing-quote.md +71 -0
  89. package/assets/patterns/reuse-first-extend.md +22 -22
  90. package/assets/patterns/seo-page.md +52 -40
  91. package/assets/patterns/seo-verify.md +48 -29
  92. package/assets/patterns/shared-code-new.md +37 -31
  93. package/assets/patterns/spec-driven-domain.md +60 -37
  94. package/assets/patterns/spec-driven-rule.md +55 -40
  95. package/assets/patterns/styling-bem-component.md +43 -32
  96. package/assets/patterns/styling-bem-layout.md +30 -24
  97. package/assets/patterns/task-flow-close.md +154 -0
  98. package/assets/patterns/task-flow-resume.md +94 -0
  99. package/assets/patterns/task-flow-start.md +129 -0
  100. package/assets/patterns/testing-e2e.md +53 -51
  101. package/assets/patterns/testing-unit.md +70 -46
  102. package/assets/patterns/translations-key.md +32 -19
  103. package/assets/patterns/ts-procedure.md +24 -25
  104. package/assets/rules/angular-patterns.md +50 -27
  105. package/assets/rules/api-layer.md +46 -28
  106. package/assets/rules/browser-verification.md +67 -48
  107. package/assets/rules/component-structure.md +43 -27
  108. package/assets/rules/dependencies.md +66 -0
  109. package/assets/rules/doc-style.md +95 -39
  110. package/assets/rules/entity-conventions.md +78 -0
  111. package/assets/rules/entity-models.md +70 -0
  112. package/assets/rules/git-workflow.azure.md +116 -0
  113. package/assets/rules/git-workflow.github.md +123 -0
  114. package/assets/rules/git-workflow.gitlab.md +113 -0
  115. package/assets/rules/lib-layers.md +56 -30
  116. package/assets/rules/lists.md +73 -0
  117. package/assets/rules/navigation.md +78 -0
  118. package/assets/rules/ownership-scope.md +63 -0
  119. package/assets/rules/permissions.md +43 -25
  120. package/assets/rules/platform-access.md +57 -29
  121. package/assets/rules/pricing.md +64 -0
  122. package/assets/rules/reuse-first.md +57 -43
  123. package/assets/rules/seo.md +51 -30
  124. package/assets/rules/shared-code.md +51 -26
  125. package/assets/rules/spec-driven.md +107 -51
  126. package/assets/rules/styling-bem.md +54 -39
  127. package/assets/rules/task-flow.md +150 -0
  128. package/assets/rules/testing.md +78 -47
  129. package/assets/rules/translations.md +48 -31
  130. package/assets/rules/typescript-conventions.md +57 -27
  131. package/assets/skills/agent-kit.md +85 -0
  132. package/assets/skills/write-a-skill.md +108 -0
  133. package/assets/templates/gate-map.sh +23 -15
  134. package/assets/templates/implementation.md +14 -8
  135. package/assets/templates/pattern.md +1 -1
  136. package/assets/templates/project.sh +32 -19
  137. package/assets/templates/rule.md +2 -2
  138. package/assets/variants.json +20 -0
  139. package/assets/workflows/feature.js +134 -0
  140. package/assets/workflows/plan.js +150 -0
  141. package/bin/agent-kit.d.ts.map +1 -1
  142. package/bin/agent-kit.js +78 -5
  143. package/bin/agent-kit.js.map +1 -1
  144. package/bin/prompt.d.ts +5 -0
  145. package/bin/prompt.d.ts.map +1 -1
  146. package/bin/prompt.js +19 -7
  147. package/bin/prompt.js.map +1 -1
  148. package/index.d.ts +1 -0
  149. package/index.d.ts.map +1 -1
  150. package/index.js +1 -0
  151. package/index.js.map +1 -1
  152. package/lib/assets.d.ts +8 -3
  153. package/lib/assets.d.ts.map +1 -1
  154. package/lib/assets.js +13 -3
  155. package/lib/assets.js.map +1 -1
  156. package/lib/catalog.d.ts +52 -5
  157. package/lib/catalog.d.ts.map +1 -1
  158. package/lib/catalog.js +104 -16
  159. package/lib/catalog.js.map +1 -1
  160. package/lib/commands.d.ts +22 -1
  161. package/lib/commands.d.ts.map +1 -1
  162. package/lib/commands.js +202 -14
  163. package/lib/commands.js.map +1 -1
  164. package/lib/companion.d.ts +5 -1
  165. package/lib/companion.d.ts.map +1 -1
  166. package/lib/companion.js +29 -2
  167. package/lib/companion.js.map +1 -1
  168. package/lib/config.d.ts +26 -9
  169. package/lib/config.d.ts.map +1 -1
  170. package/lib/config.js +41 -15
  171. package/lib/config.js.map +1 -1
  172. package/lib/freshness.d.ts +14 -0
  173. package/lib/freshness.d.ts.map +1 -0
  174. package/lib/freshness.js +116 -0
  175. package/lib/freshness.js.map +1 -0
  176. package/lib/hooks-map.d.ts +27 -0
  177. package/lib/hooks-map.d.ts.map +1 -0
  178. package/lib/hooks-map.js +77 -0
  179. package/lib/hooks-map.js.map +1 -0
  180. package/lib/integrity.d.ts +36 -0
  181. package/lib/integrity.d.ts.map +1 -0
  182. package/lib/integrity.js +44 -0
  183. package/lib/integrity.js.map +1 -0
  184. package/lib/picker.d.ts +11 -1
  185. package/lib/picker.d.ts.map +1 -1
  186. package/lib/picker.js +44 -6
  187. package/lib/picker.js.map +1 -1
  188. package/lib/sync.d.ts +26 -0
  189. package/lib/sync.d.ts.map +1 -1
  190. package/lib/sync.js +59 -4
  191. package/lib/sync.js.map +1 -1
  192. package/lib/variants.d.ts +44 -0
  193. package/lib/variants.d.ts.map +1 -0
  194. package/lib/variants.js +82 -0
  195. package/lib/variants.js.map +1 -0
  196. package/package.json +1 -1
  197. package/rt-tools-agent-kit-0.5.0.tgz +0 -0
  198. package/assets/laws/admin-lists.md +0 -35
  199. package/assets/laws/admin-navigation.md +0 -38
  200. package/assets/patterns/git-workflow-commit.md +0 -175
  201. package/assets/rules/git-workflow.md +0 -106
  202. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: SessionStart startup|resume|compact|clear
3
+ # SessionStart: состояние незаконченной работы уезжает в контекст на каждом запуске сессии.
4
+ #
5
+ # Памятью это не держится по той же причине, что и словарь: замысел читают перед правкой
6
+ # файла, а разговор с владельцем начинается с вопроса — и заход отвечает, не зная, что работа
7
+ # уже наполовину сделана. Здесь замысел и ход работы приходят до первой реплики, и владельцу
8
+ # не приходится пересказывать то, что уже записано.
9
+ #
10
+ # Разбор просьбы (`grill.md`) отдаётся путём, а не текстом: он неизменен, объёмен и нужен
11
+ # реже остальных.
12
+ #
13
+ # FAIL-OPEN: нет `jq`, не git-репозиторий, нет папки задачи — выходим молча. Сессия важнее
14
+ # контекста.
15
+
16
+ ROOT="${CLAUDE_PROJECT_DIR:-.}"
17
+ command -v jq >/dev/null 2>&1 || exit 0
18
+ cd "$ROOT" 2>/dev/null || exit 0
19
+ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
20
+
21
+ branch="$(git branch --show-current 2>/dev/null)"
22
+ [ -z "$branch" ] && exit 0
23
+
24
+ # Профиль дерева: сперва умолчание пакета, поверх него — надстройка проекта, если она есть.
25
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" "$ROOT/.claude/rt-kit/defaults/project.sh" "$ROOT/.claude/rt-kit/project.sh"; do
27
+ # shellcheck disable=SC1090
28
+ [ -f "$profile" ] && . "$profile" 2>/dev/null
29
+ done
30
+
31
+ TASKS_DIR="${RT_TASKS_DIR:-docs/tasks}"
32
+ [ -z "$TASKS_DIR" ] && exit 0
33
+
34
+ DIR="$TASKS_DIR/$branch"
35
+ PLAN="$DIR/plan.md"
36
+ PROGRESS="$DIR/progress.md"
37
+ GRILL="$DIR/grill.md"
38
+
39
+ emit() {
40
+ jq -Rs '{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:.}}' 2>/dev/null
41
+ }
42
+
43
+ # Ветка под задачу без папки — работа идёт мимо. Сессию не рвём: SessionStart, отбивающий
44
+ # запуск, оставляет владельца без агента вовсе, а правку кода поймает `task-flow-guard`.
45
+ if [ ! -d "$DIR" ]; then
46
+ if command -v rt_task_branch_ok >/dev/null 2>&1 && rt_task_branch_ok "$branch"; then
47
+ {
48
+ printf 'РАБОТА БЕЗ ПАПКИ ЗАДАЧИ.\n\n'
49
+ printf 'Ветка `%s` названа задачей, а `%s/` нет: ход работы записывать некуда,\n' "$branch" "$DIR"
50
+ printf 'и следующий заход начнёт с расспросов владельца.\n\n'
51
+ printf 'Собрать с образца:\n\n cp -r %s/_template %s\n\n' "$TASKS_DIR" "$DIR"
52
+ printf 'Правку кода приложения до этого отбивает гард. Правило — скил `task-flow`.\n'
53
+ } | emit
54
+ fi
55
+ exit 0
56
+ fi
57
+
58
+ # Порог объёма. Ход работы растёт с каждым заходом, и на десятом заходе целиком он стоит
59
+ # дороже, чем даёт. Перевалив порог, отдаём «Где стоим» и последние записи.
60
+ LIMIT=40000
61
+ size=0
62
+ for file in "$PLAN" "$PROGRESS"; do
63
+ [ -f "$file" ] || continue
64
+ size=$((size + $(wc -c <"$file" 2>/dev/null || echo 0)))
65
+ done
66
+
67
+ {
68
+ printf 'СОСТОЯНИЕ РАБОТЫ — ветка `%s`, папка `%s/`.\n\n' "$branch" "$DIR"
69
+ printf 'Это записано прошлыми заходами. Владельца о том, что здесь есть, не спрашивают.\n'
70
+ printf 'Отметка о сделанном — только в `progress.md`; `plan.md` по ходу не правится.\n'
71
+ printf 'Как ведётся работа — правило `task-flow`, возвращение к ней — паттерн `task-flow-resume`.\n\n'
72
+
73
+ if [ -f "$GRILL" ]; then
74
+ printf 'Разбор просьбы владельца — `%s`, читается по надобности.\n\n' "$GRILL"
75
+ fi
76
+
77
+ if [ -f "$PLAN" ]; then
78
+ printf -- '--- ЗАМЫСЕЛ (`%s`) ---\n\n' "$PLAN"
79
+ if [ "$size" -le "$LIMIT" ]; then
80
+ cat "$PLAN"
81
+ else
82
+ sed -n '1,60p' "$PLAN"
83
+ printf '\n<обрезано по объёму — читается целиком: %s>\n' "$PLAN"
84
+ fi
85
+ printf '\n'
86
+ fi
87
+
88
+ if [ -f "$PROGRESS" ]; then
89
+ printf -- '--- ХОД РАБОТЫ (`%s`) ---\n\n' "$PROGRESS"
90
+ if [ "$size" -le "$LIMIT" ]; then
91
+ cat "$PROGRESS"
92
+ else
93
+ # Раздел «Где стоим» перезаписывается каждым заходом и переживает любой объём.
94
+ awk '/^## Где стоим/{f=1} f&&/^## /&&!/^## Где стоим/{exit} f' "$PROGRESS"
95
+ printf '\n<обрезано по объёму. Последние записи:>\n\n'
96
+ tail -40 "$PROGRESS"
97
+ printf '\n<читается целиком: %s>\n' "$PROGRESS"
98
+ fi
99
+ fi
100
+ } | emit
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: PreToolUse Edit|Write|MultiEdit
3
+ # PreToolUse guard for Edit|Write|MultiEdit: код не пишется раньше замысла.
4
+ #
5
+ # Работа идёт много заходов, и между ними исполнитель не помнит ничего. Замысел, лежащий на
6
+ # диске, — единственное, что переживает перерыв: изменения к этому моменту бывают не
7
+ # закоммичены, отчёт не открыт, а очередь работ показывает задачу начатой и молчит о том, что
8
+ # внутри неё сделано.
9
+ #
10
+ # Гард требует три вещи и ровно их: папку задачи по имени ветки, замысел в ней и названную в
11
+ # замысле договорённость о продукте. Полноту написанного он не судит — это за владельцем
12
+ # (решения в законе `docs/constitution/work-conduct.md`).
13
+ #
14
+ # Правило целиком — скил `task-flow`.
15
+ #
16
+ # Осознанный выход есть: строка `**Поведение:** не меняется — <причина>` в замысле снимает
17
+ # требование договорённости. Пустая причина не принимается, как и у `Docs-skip:`.
18
+ #
19
+ # FAIL-OPEN: нет jq, не git-репозиторий, битый ввод, чужой инструмент → пропуск. Сломанный
20
+ # гард не должен мешать работать.
21
+
22
+ input="$(cat 2>/dev/null)"
23
+ [ -z "$input" ] && exit 0
24
+ command -v jq >/dev/null 2>&1 || exit 0
25
+
26
+ tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
27
+ case "$tool" in
28
+ # Инструмент редактора заводит файл теми же двумя данными, только называет их иначе —
29
+ # без этой ветки правка шла бы мимо гарда сменой инструмента.
30
+ Edit | Write | MultiEdit | mcp__webstorm__create_new_file) ;;
31
+ *) exit 0 ;;
32
+ esac
33
+
34
+ path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.pathInProject // empty' 2>/dev/null)"
35
+ [ -z "$path" ] && exit 0
36
+ case "$path" in
37
+ /*) ;;
38
+ ?*) path="${CLAUDE_PROJECT_DIR:-.}/$path" ;;
39
+ esac
40
+
41
+ # Профиль дерева: сперва умолчание пакета, поверх него — надстройка проекта, если она есть.
42
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
43
+ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/project.sh"; do
44
+ # shellcheck disable=SC1090
45
+ [ -f "$profile" ] && . "$profile" 2>/dev/null
46
+ done
47
+
48
+ # Признак «правка меняет поведение» — путь, а не оценка на глаз: оценку назначает тот, кому
49
+ # она мешает, и порог плывёт. Где живёт код приложения, знает профиль: правила, тексты, обвязка
50
+ # и зависимости под требование не попадают — иначе разбор задачи нельзя было бы вести до
51
+ # заведения ветки.
52
+ command -v rt_is_app_code >/dev/null 2>&1 || exit 0
53
+ rt_is_app_code "$path" || exit 0
54
+
55
+ # Каталог папок задач: у дерева он свой, но имя обычно общее.
56
+ tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
57
+
58
+ deny() {
59
+ jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
60
+ || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "$1"
61
+ exit 0
62
+ }
63
+
64
+ # Ветку смотрим там же, где пойдёт правка: у worktree она своя.
65
+ workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
66
+ [ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
67
+ cd "$workdir" 2>/dev/null || exit 0
68
+ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
69
+
70
+ branch="$(git branch --show-current 2>/dev/null)"
71
+ [ -z "$branch" ] && exit 0 # detached HEAD — не про наш случай
72
+
73
+ if command -v rt_task_branch_ok >/dev/null 2>&1 && ! rt_task_branch_ok "$branch"; then
74
+ deny "BLOCKED by task-flow: правка кода идёт в ветке под задачу, а текущая ветка — '${branch}'. Заведи задачу (npm run task:new -- --title '…' --slug <slug>) и ветку под её номером, затем повтори. Правило — скил task-flow."
75
+ fi
76
+
77
+ root="$(git rev-parse --show-toplevel 2>/dev/null)"
78
+ [ -z "$root" ] && exit 0
79
+ dir="$root/$tasks_dir/$branch"
80
+ plan="$dir/plan.md"
81
+
82
+ if [ ! -f "$plan" ]; then
83
+ deny "BLOCKED by task-flow: нет замысла — '${tasks_dir}/${branch}/plan.md'. Собери папку задачи с образца (cp -r ${tasks_dir}/_template ${tasks_dir}/${branch}) и заполни шапку, след задачи и этапы, затем повтори. Правило — скил task-flow."
84
+ fi
85
+
86
+ # Строка обхода: поведение не меняется, договорённость о продукте не нужна. Причина обязана
87
+ # стоять — без неё обход становится умолчанием.
88
+ if grep -qE '^\*\*Поведение:\*\*[[:space:]]*не меняется[[:space:]]*—[[:space:]]*\S' "$plan" 2>/dev/null; then
89
+ exit 0
90
+ fi
91
+
92
+ draft="$(sed -n 's/^\*\*Драфт:\*\*[[:space:]]*`\([^`]*\)`.*/\1/p' "$plan" 2>/dev/null | head -1)"
93
+
94
+ if [ -z "$draft" ]; then
95
+ deny "BLOCKED by task-flow: в '${tasks_dir}/${branch}/plan.md' не названа договорённость о продукте. Заведи её в docs/specs/<домен>/proposed/<фича>/ и укажи строкой '**Драфт:** \`путь\`'. Если правка поведения не меняет — поставь '**Поведение:** не меняется — <причина владельца>'. Правило — скил task-flow."
96
+ fi
97
+
98
+ case "$draft" in
99
+ /*) draft_path="$draft" ;;
100
+ *) draft_path="$root/$draft" ;;
101
+ esac
102
+
103
+ if [ -e "$draft_path" ]; then
104
+ exit 0
105
+ fi
106
+
107
+ # Договорённость, влитая в спек домена, с диска уходит — так и задумано: в главной ветке
108
+ # директории «предложено» быть не должно. Но замысел на неё ссылается до конца работы, и без
109
+ # этой развилки последний коммит отчёта запирал бы ветку: ни правки по замечаниям разбора, ни
110
+ # записи в журнал изменений после вливания уже не сделать.
111
+ #
112
+ # Влитое от незаведённого отличает история ветки: путь, которого в ней никогда не было,
113
+ # договорённостью не был. Спросить об этом нечем, кроме git, поэтому нет git — отказ остаётся.
114
+ if git -C "$root" log --oneline -1 -- "$draft" 2>/dev/null | grep -q .; then
115
+ exit 0
116
+ fi
117
+
118
+ deny "BLOCKED by task-flow: замысел называет договорённость '${draft}', а её на диске нет и в истории ветки не было. Заведи её с образца (docs/specs/_template) или поправь путь в '${tasks_dir}/${branch}/plan.md'. Правило — скил task-flow."
@@ -1,4 +1,4 @@
1
- # Доступ
1
+ # Закон о доступе
2
2
 
3
3
  Кто что может делать: вход владельца, права на действия, поведение публичных путей.
4
4
  Правила держат все процедуры контракта и все разделы админки.
@@ -32,6 +32,3 @@
32
32
  - **Пока права не получены, админка ничего не прячет.** Пустая шапка после сетевого сбоя
33
33
  выглядит как сломанная админка и не оставляет выхода; запрос без права всё равно
34
34
  отобьётся на сервере.
35
- - **Отобранное право перестаёт действовать не позже, чем истекает вход.** Права, вписанные во
36
- вход и живущие до его конца, оставляют раздел открытым тому, у кого право уже забрали, — и
37
- тем дольше, чем длиннее вход. Отзыв, который замечают только при следующем входе, — не отзыв.
@@ -1,4 +1,4 @@
1
- # Локали и переводы
1
+ # Закон о локалях и переводах
2
2
 
3
3
  Как в системе устроены языки: адреса публичного сайта, подписи интерфейса, переводы
4
4
  контента и язык админки. Правила держат сайт, админку, письма и документ-подтверждение
@@ -31,5 +31,3 @@
31
31
  ввода при сохранении объекта.
32
32
  - **У локали есть начальная валюта показа.** Дальше валюту выбирает гость, и его выбор
33
33
  сильнее умолчания локали.
34
- - **Локаль страницы определяется её адресом, а не языком браузера.** Иначе один адрес отдаёт
35
- разным гостям разный текст, и кэшировать его нечем: в кэш попадает то, что увидел первый.
@@ -0,0 +1,41 @@
1
+ # Закон о деньгах
2
+
3
+ Как в системе устроены суммы. Правила держат расчёт цены, заказы, письма, документы-основания
4
+ и сводки одновременно, и разойтись им нельзя: сумма, названная в трёх местах по-разному, — это
5
+ не три числа, а одно сломанное.
6
+
7
+ ## Терминология
8
+
9
+ | Термин | Определение |
10
+ | --------------- | ------------------------------------------------------------------------------- |
11
+ | Валюта хранения | Единственная валюта, в которой сумма записана и посчитана. Одна на всю систему |
12
+ | Валюта показа | Валюта, в которой сумму видит пользователь. Может отличаться от валюты хранения |
13
+ | Подытог | Сумма до скидок |
14
+ | Итог | Сумма после применённой скидки — то, что называется ценой |
15
+
16
+ ## Статьи
17
+
18
+ - **Сумма хранится в целых единицах валюты хранения.** Дробная часть даёт расхождение между
19
+ подытогом и итогом: то, что показано построчно, перестаёт складываться в то, что показано
20
+ внизу. Если дробная часть в предметной области значима, единицей хранения становится она
21
+ сама, а не доля от целого.
22
+ - **Валюта хранения одна на систему, и она объявлена.** Две валюты хранения означают, что
23
+ каждое сравнение сумм — это скрытый пересчёт по курсу, которого в момент сравнения может не
24
+ быть вовсе.
25
+ - **Округление происходит один раз — при расчёте цены.** Сумма на экране, сумма в письме и
26
+ сумма в хранилище — одно и то же число, а не три результата одного пересчёта.
27
+ - **Из подходящих скидок применяется одна — наибольшая.** Сложение дало бы суммы, которых
28
+ никто не закладывал, и объяснить пользователю итог стало бы нечем.
29
+ - **При равной выгоде побеждает та скидка, которую пользователь ввёл руками.** Он ждёт
30
+ подтверждения своему действию, а «код не сработал» при той же итоговой цене читается как
31
+ поломка.
32
+ - **Скидка не уводит цену ниже нуля.** Скидка суммой ограничена самим подытогом.
33
+ - **Пересчёт в чужую валюту не хранится.** Хранится сумма в валюте хранения и код валюты
34
+ показа; само число вычисляется на показ. Сохранённое устарело бы вместе с курсом, а по виду
35
+ оставалось бы точным.
36
+ - **Оплата идёт в валюте хранения; остальные валюты — справка.** Ни подтверждение, ни документ
37
+ не называют справочное число суммой к оплате.
38
+ - **Валюта показа следует за пользователем.** Он выбрал её на экране — в письме сумма
39
+ пересчитана в неё же. Письмо в другой валюте читается как другая цена.
40
+ - **Курс недоступен — сумма показывается в валюте хранения без пересчёта.** Пересчёт по
41
+ неизвестно какому курсу хуже отсутствующего: отсутствие видно, а неверный курс — нет.
@@ -0,0 +1,32 @@
1
+ # Закон о владеющей сущности
2
+
3
+ Как система ведёт себя, когда владеющих сущностей больше одной. Владеющая сущность — та, кому
4
+ принадлежат записи остальных доменов: площадка, объект, организация, точка. Правило держат все
5
+ эти домены сразу, и разойтись им нельзя.
6
+
7
+ ## Терминология
8
+
9
+ | Термин | Определение |
10
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
11
+ | Владеющая сущность | Запись, которой принадлежат записи остальных доменов. Не настройка: у неё свой идентификатор, свои права и своя видимость |
12
+ | Действующая | Та, что видна пользователю снаружи |
13
+ | Скрытая | Заведённая, но снаружи не показанная. Её записи никуда не деваются |
14
+ | Сводка | Экран, отвечающий сразу по всем действующим: очередь работ, статистика, дашборд |
15
+
16
+ ## Статьи
17
+
18
+ - **Запись принадлежит владеющей сущности, а не системе.** Настроек, действующих сразу на все,
19
+ нет: заведение второй сущности не меняет поведение первой.
20
+ - **Пустой идентификатор в запросе означает единственную действующую.** Пока она одна, клиент
21
+ вправе её не называть — и не обязан меняться в тот день, когда появится вторая.
22
+ - **При двух и более действующих запрос обязан назвать сущность.** Умолчание перестаёт быть
23
+ однозначным, и запрос отбивается как неверный, а не выбирает первую попавшуюся.
24
+ - **Названная явно находится, даже если она скрыта.** У скрытой остаются записи, деньги и
25
+ переписка, и добраться до них надо как раз тогда, когда снаружи её уже нет.
26
+ - **Там, где показана сводка, пустой идентификатор означает все действующие.** Очередь работ,
27
+ статистика и дашборд отвечают по всему набору, а не требуют сперва выбрать одну.
28
+ - **Когда действующих нет вовсе, запрос отбивается.** Скрытая вместо отсутствующей действующей
29
+ не подставляется: показанное в этот момент выглядело бы работающим, а на деле показывало бы
30
+ снятое с показа.
31
+ - **Выбор сущности виден на экране и действует на всё, что говорит об одной.** Пользователь,
32
+ который не видит, о какой сущности идёт речь, читает чужие числа как свои.
@@ -1,4 +1,4 @@
1
- # Видимость в поиске
1
+ # Закон о видимости в поиске
2
2
 
3
3
  Как публичный сайт выглядит для поисковика. Страница живёт в нескольких языках под одним
4
4
  доменом, и от разметки зависит, считает поисковик эти адреса одной страницей на разных
@@ -1,4 +1,4 @@
1
- # Устройство кода
1
+ # Закон об устройстве кода
2
2
 
3
3
  Что должно быть верно про сам код независимо от того, что он делает. Закон нужен потому, что
4
4
  код читают чаще, чем пишут: имя, по которому не видно рода объявления, и тип, объявленный
@@ -8,14 +8,15 @@
8
8
 
9
9
  - **Имя объявления говорит, какого оно рода.** Без этого род объявления выясняется переходом
10
10
  к нему, и на каждом чтении заново.
11
+ - **Имя файла, обещающее род объявления, это объявление в нём находит.** Списки, деревья
12
+ каталогов и сообщения об ошибках показывают имя вместо содержимого, и обещание, которого
13
+ файл не держит, обходится дороже отсутствующего.
11
14
  - **Источник значения, за которым следят, виден по его имени.** Иначе подписка на него
12
15
  выглядит как обычное чтение, и её забывают снять.
13
16
  - **Тип берётся из того пакета, где объявлен.** Своя копия чужого типа расходится с
14
17
  оригиналом молча, а компилируется из них только одна.
18
+ - **Значение не объявляется подходящим в обход проверки типа.** Приведение через промежуточное
19
+ «неизвестно» отключает сверку намеренно и принимает что угодно; там, где иначе нельзя,
20
+ причина названа рядом.
15
21
  - **Отметка об устаревании — повод убрать, а не повод оставить.** Устаревшее объявление,
16
22
  которое молча продолжает работать, переживает того, кто его пометил.
17
- - **Значение проверяется, а не объявляется подходящим.** Объявить значение подходящим можно
18
- где угодно и о чём угодно: это принимает любую строку и компилируется, а расходится с
19
- правдой уже на работающем приложении.
20
- - **Имя файла говорит, что в нём лежит.** Файл под чужим именем находится поиском, а
21
- открывается не тем, чего от него ждали, — и так на каждом чтении заново.
@@ -1,4 +1,4 @@
1
- # Поставка
1
+ # Закон о поставке
2
2
 
3
3
  Как правка доезжает до работающего приложения. Закон держит и историю изменений, и то, что
4
4
  в этот момент видит пользователь: неудачная выкатка отличается от удачной только тем, что
@@ -6,15 +6,65 @@
6
6
 
7
7
  ## Статьи
8
8
 
9
+ - **Правка начинается с задачи, видимой в очереди работ.** Заведённой задачи мало: ту, что в
10
+ очередь не попала, никто не видит, и работа за ней не планировалась.
9
11
  - **Правка попадает в главную ветку только через отдельную ветку.** Прямая запись в главную
10
12
  лишает правку и обсуждения, и возможности откатить её одним движением.
13
+ - **У задачи одна ветка, у ветки одна задача.** Откат снимает всё, что въехало этой веткой,
14
+ разом: две задачи в ней откатятся только вместе, а задача, въехавшая двумя ветками, после
15
+ отката одной останется наполовину сделанной — и в очереди работ этого не видно. Работа,
16
+ которая в одну ветку не влезает, делится на задачи до того, как ветка заводится. Признак
17
+ деления — раздельный откат, а не объём: числа файлов, строк или коммитов, за которым работа
18
+ становится двумя задачами, нет. Правка одного рода остаётся одной задачей, сколько бы файлов
19
+ она ни задела.
20
+ - **Две задачи, которые чинятся одной правкой, — одна задача.** Вторая стирается вместе со
21
+ своим номером, а то, чего в первой не было, дописывается в неё до этого. Две строки об
22
+ одной работе хуже дыры в нумерации: по ним потом не понять, что сделано, а что нет. Слить
23
+ их можно, пока правка не въехала в главную ветку; после — обе остаются как есть.
24
+ - **Задача, ветка под неё и отчёт о сделанном несут один и тот же номер в своих названиях.**
25
+ Иначе одну работу приходится узнавать по тексту названия, а в списке из полусотни строк это
26
+ делается по памяти и с ошибками.
27
+ - **Номер пишется всюду одинаково: ключ задач, дефис, номер.** Заголовок задачи и отчёта
28
+ начинается с этой пары в квадратных скобках, имя ветки — с неё же. Одна форма, а не три
29
+ похожих, потому что номер читают не только глазами: из имени ветки его достаёт гард, из
30
+ заголовка — сверка очереди. Формы, выведенные порознь, расходятся молча и не отказывают, а
31
+ перестают узнавать номер: проверка, которая должна была найти работу без задачи, пропускает
32
+ всё подряд.
33
+ - **Ключ задач дерево называет само, но назвать обязано.** В форме имени это единственное, что
34
+ у каждого дерева своё, — и единственное, что настраивается. Не названный ключ не даёт ни
35
+ поблажки, ни умолчания: работа с очередью отказывает и говорит, где он задаётся. Пустой ключ
36
+ хуже отсутствующей проверки — от имени остаётся огрызок, которому ничто не отвечает, и
37
+ правильно названной не выглядит ни одна задача.
38
+ - **У задачи есть исполнитель с момента её заведения.** Задача без исполнителя выглядит
39
+ ничьей: по очереди работ не видно, кто её взял, и заведённая по ходу правка теряется среди
40
+ чужих.
41
+ - **Состояние задачи в очереди работ отвечает тому, что с ней происходит.** Взятая в работу
42
+ видна взятой, а та, отчёт по которой ждёт разбора, — ждущей разбора. Иначе очередь показывает
43
+ один и тот же вид у нетронутого, у делаемого прямо сейчас и у сделанного: работа берётся
44
+ второй раз, а отчёт стоит неразобранным, пока про него не вспомнят. Состояние переставляется
45
+ в тот момент, когда работа переходит на следующий шаг, а не приводится в порядок потом:
46
+ очередь читают между этими моментами, а не после них.
11
47
  - **Попадание правки в главную ветку означает выкатку.** Всё, от чего правка зависит снаружи
12
48
  кода — переменные окружения, секреты, записи имён, — ставится до этого момента, а не после.
49
+ - **Признак режима исполнения объявлен в самом артефакте развёртывания, а не только в составе
50
+ его запуска.** Артефакт поднимают и мимо состава — руками, при разборе, на чужой машине, — и
51
+ без объявления он в этот момент считает себя отладочным, не сказав об этом ничего.
13
52
  - **Выкатывается образ того коммита, который выкатывают.** Умолчание «последний» отстаёт от
14
53
  главной ветки, и приложение молча возвращается к прежней версии, продолжая отвечать.
54
+ - **Из одного и того же коммита всегда ставятся одни и те же зависимости.** Если версия задана
55
+ диапазоном, установка сегодня и установка через неделю дадут разный код: сборка сломается
56
+ сама собой, и откатывать будет нечего. Обновление зависимости — обычная правка: у неё есть
57
+ автор, описание и откат.
15
58
  - **Порядок изменений хранилища проверяется с пустого места.** На уже работающем хранилище
16
59
  неверный порядок незаметен: он проявляется только при развёртывании с нуля.
60
+ - **Проверка перед отправкой смотрит на содержимое репозитория, а не на состояние машины, где
61
+ она запущена.** На машине законно лежат недоделки, личные настройки и файлы вне истории.
62
+ Проверка, которая их читает, отбивает правку из-за того, чего в репозитории нет, и молчит о
63
+ том, что в нём есть. Проверка перед отправкой и конвейер выкатки судят по одному и тому же —
64
+ иначе «сошлось» значит в этих двух местах разное.
17
65
  - **Документ едет вместе с правкой, которую он описывает.** Ни сборка, ни проверки текстов
18
66
  не читают, поэтому расхождение копится молча и потом выглядит действующей справкой.
19
- - **Отдельная ветка называет задачу, которую закрывает.** Иначе связь правки с причиной живёт
20
- только в памяти автора, и через полгода по истории не сказать, зачем это сделано.
67
+ - **Отчёт о сделанном остаётся верным до самого слияния.** Он описывает дерево на день, когда
68
+ его написали, а разбора ждёт днями: за это время главная ветка вливается в ветку, и
69
+ утверждение отчёта о соседних файлах становится неправдой молча — тел отчётов не читает ни
70
+ одна проверка. Всё, что вливается в ветку после публикации отчёта, — повод перечитать его.
@@ -1,60 +1,54 @@
1
- # Правка сущности
1
+ # Закон о правке сущности
2
2
 
3
- Как ведёт себя приложение, когда владелец создаёт или меняет запись: объект, бронь,
4
- подписку на календарь, адрес страницы. Поведение одинаково во всех разделах админки и ни
5
- одному домену не принадлежит — спек домена описывает только то, что у него своего.
3
+ Как ведёт себя приложение, когда пользователь создаёт или меняет запись. Ведёт оно себя
4
+ одинаково везде, где правят записи: это общее поведение, а не особенность одной его части.
6
5
 
7
- Попадание в запись из набора и объём запрашиваемых данных — предмет других законов.
6
+ Попадание в запись из списка и объём запрашиваемых данных — предмет других законов.
8
7
 
9
8
  ## Статьи
10
9
 
11
- - **Асайд закрывается только после успешного запроса.** Закрытие сигнал «сохранено»,
12
- и закрыть его до ответа значит соврать.
13
- - **Пока запрос идёт, кнопка сохранения показывает спиннер и недоступна.** Второе нажатие
14
- иначе шлёт второй запрос по той же форме.
15
- - **Отказ остаётся внутри асайда, рядом с полями.** Асайд не закрывается, введённое
16
- сохраняется, кнопка снова доступна владелец правит и повторяет, а не набирает заново.
17
- - **Новый запрос гасит прежнюю ошибку.** Иначе рядом с работающей формой висит сообщение
18
- о прошлой неудаче.
19
- - **Создание закрывает асайд всегда, редактирование — по решению экрана.** Созданной
20
- записи на экране ещё нет, и оставаться в форме не над чем; при правке экран может
21
- оставить асайд открытым и перечитать запись.
22
- - **Успешная правка подтверждается тостом.** Закрывшийся асайд сам по себе не говорит,
23
- сохранилось ли, — особенно когда список за ним обновляется не сразу.
24
- - **Кнопка записи недоступна, пока в форме ничего не изменили.** Иначе владелец отправляет
25
- запрос без правок и получает подтверждение ни о чём.
26
- - **Закрытие панели с несохранёнными правками спрашивает, что с ними сделать: закрыть без
27
- сохранения, закрыть с сохранением или остаться.** Вопрос задаётся на каждом из четырёх
28
- путей закрытия.
29
- - **Кнопка закрытия называет судьбу правок: «Закрыть и не сохранять» на панели с записью и
30
- «Закрыть» на панели только для просмотра.**
31
- - **Ссылка на запись, которой уже нет, не оставляет пустой панели.**
32
- - **Панель узнаёт, что показывать, из адреса: идентификатор записи стоит в нём.** Иначе панель не
33
- открыть по ссылке и не восстановить после перезагрузки.
34
- - **У панели есть шапка с заголовком и футер, и в футере хотя бы кнопка закрытия.**
35
- - **Заголовок панели называет действие, а имя записи стоит надстрочником над ним.** Заголовок
36
- из одного названия записи не говорит, что сейчас произойдёт.
37
- - **Создание и правка названы в заголовке по-разному.**
38
- - **Связанная запись открывается из панели ссылкой.**
39
- - **Переход по ссылке из панели с несохранёнными правками спрашивает о них так же, как
40
- закрытие.**
41
- - **Кнопка записи в футере одна: «Создать» при создании, «Сохранить» при правке.** Она стоит у
42
- противоположного края от кнопки закрытия.
43
- - **Панель закрывается четырьмя путями: кнопкой в шапке, кнопкой в футере, нажатием мимо неё и
44
- клавишей Esc.**
45
- - **Закрытая панель нажатий не перехватывает.** Она стоит за краем экрана, ничего не видно, и
46
- страница под ней перестала бы отвечать на нажатия молча.
47
- - **Прокручивается только содержимое: шапка и футер видны всегда.**
48
- - **Поля во всех панелях отбиты от края одинаково и получают одну ширину.** Прокручивается
49
- при этом всё содержимое целиком, поэтому обводка фокуса у крайнего поля видна полностью.
50
- - **Пока запись читается, поля показаны скелетонами.**
51
- - **Запись, которую можно только смотреть, показана данными, а не полями ввода.** Поле ввода,
52
- которое не принимает ввод, владелец сначала пробует заполнить.
53
- - **Непонятное объясняется на месте: подсказкой под полем или сообщением над формой.**
54
- - **Действия над записью доступны из самой записи, а не только из набора.** Они стоят в шапке
55
- панели и при числе больше двух прячутся под одну кнопку меню; иначе открытая запись — тупик,
56
- из которого приходится возвращаться в набор ради каждого действия.
57
- - **Длинная форма разложена вкладками, и вкладки остаются на месте при прокрутке.** Разделы
58
- подряд одной полосой требуют прокрутить всю форму до нижнего.
59
- - **Действий, относящихся к содержимому одной вкладки, не заводится.** С закрытой вкладки
60
- такое действие не видно, а на открытой спорит с общей кнопкой записи.
10
+ - **Форма закрывается только после того, как запись сохранена.** Закрытие означает
11
+ «сохранено», поэтому закрывать её до ответа сервера нельзя.
12
+ - **Пока идёт сохранение, отправить форму второй раз нельзя, и пользователь видит, что запрос
13
+ ещё не закончился.** Иначе повторное нажатие отправит те же данные ещё раз.
14
+ - **Если сохранить не удалось, введённое остаётся на месте.** Ошибка показана рядом с полями,
15
+ форма открыта, и пользователь исправляет данные, а не набирает их заново.
16
+ - **Новая попытка убирает сообщение о прошлой ошибке.** Иначе рядом с работающей формой висит
17
+ сообщение о неудаче, которой уже нет.
18
+ - **После создания форма закрывается всегда, после изменения — по решению экрана.** Новой
19
+ записи на экране ещё нет, и держать форму открытой не над чем; при изменении экран может
20
+ оставить её открытой и перечитать запись.
21
+ - **Об успехе говорит сообщение, а не то, что форма исчезла.** По одному закрытию непонятно,
22
+ сохранилось ли, — тем более что список за формой обновляется не сразу.
23
+ - **Форму, в которой ничего не меняли, отправить нельзя.** Иначе пользователь шлёт запрос без
24
+ правок и получает подтверждение того, чего не делал.
25
+ - **Если пользователь уходит с несохранёнными правками, приложение спрашивает, что с ними
26
+ делать.** Ответа три: уйти без сохранения, сохранить и уйти, остаться. Спрашивается это,
27
+ каким бы способом пользователь ни уходил.
28
+ - **По подписи действия, которое уводит с формы, понятно, что станет с введённым.** Если на
29
+ форме с правками и на форме только для просмотра написано одно и то же слово, пользователь
30
+ не знает, потеряет он введённое или нет.
31
+ - **Если запись по ссылке уже удалена, пользователь видит объяснение, а не пустой экран.**
32
+ - **Какую запись показывать, форма берёт из адреса.** Иначе её не открыть по ссылке и не
33
+ восстановить после перезагрузки.
34
+ - **При прокрутке длинной формы её название и её действия остаются на виду.**
35
+ - **По названию формы понятно, что сейчас произойдёт, а не только с какой записью работает
36
+ пользователь.** По одному имени записи непонятно, создают её или меняют.
37
+ - **Создание и изменение названы по-разному.**
38
+ - **Сохранить введённое можно одним действием.** Оно стоит в стороне от того, которое уводит с
39
+ формы, чтобы их не перепутали.
40
+ - **Уйти с формы можно несколькими способами:** мышью, с клавиатуры и нажатием вне формы.
41
+ - **Закрытая форма нажатий не перехватывает.** Её не видно, а страница под ней перестала бы
42
+ отвечать без всякого объяснения.
43
+ - **У всех полей формы одинаковые отступы от края и одинаковая ширина.** Содержимое
44
+ прокручивается целиком, поэтому рамка фокуса у крайнего поля не обрезается.
45
+ - **Пока запись загружается, на месте полей показано, что идёт загрузка.**
46
+ - **Запись, которую нельзя менять, показана текстом, а не полями ввода.** Поле, которое не
47
+ принимает ввод, пользователь сначала пробует заполнить.
48
+ - **На связанную запись можно перейти прямо из формы.** Переход такой же уход с формы, как
49
+ закрытие, поэтому о несохранённых правках спрашивается так же; выбравшему сохранить и уйти
50
+ связанная запись открывается после успешного сохранения.
51
+ - **Пояснение стоит рядом с тем, что вызвало вопрос.**
52
+ - **Отправляемость формы и вопрос при уходе решаются одним признаком.** Два разных ответа на
53
+ вопрос «изменилось ли что-нибудь» рано или поздно расходятся, и форма либо шлёт пустую
54
+ правку, либо молча теряет введённое.
@@ -1,10 +1,10 @@
1
- # Модели сущностей
1
+ # Закон о моделях сущностей
2
2
 
3
3
  Сколько данных приложение запрашивает и отдаёт на каждом экране: строка списка, открытая
4
- запись, пункт выпадающего списка. Одна и та же сущность выглядит на них по-разному, и разница
5
- эта не украшение — от неё зависит, сколько владелец ждёт список.
4
+ запись, пункт выпадающего списка. Одна и та же сущность выглядит на них по-разному, и
5
+ разница эта не украшение — от неё зависит, сколько пользователь ждёт список.
6
6
 
7
- Как список показывает записи и как ведёт себя панель правки — предмет других законов.
7
+ Как список показывает записи и как ведёт себя форма правки — предмет других законов.
8
8
 
9
9
  ## Статьи
10
10
 
@@ -15,13 +15,3 @@
15
15
  - **Между сторонами стоит перевод, и экраны читают только вторую.**
16
16
  - **Пустое значение выражается пустой строкой или нулём, а не отсутствием поля.** Необязательных
17
17
  скаляров в контракте нет, и «не задано» у каждого поля значит своё.
18
- - **У сущности два уровня: короткий и полный.** Короткий — для строки набора, выпадающего
19
- списка и подсказок ввода, полный — для открытой записи. Одна модель на все случаи тянет в
20
- набор то, что в нём не показано: изображения, длинные тексты, вложенные наборы.
21
- - **Следующий уровень расширяет предыдущий, а не повторяет его поля.** Повторённое поле
22
- расходится при первой же правке, и какое из двух объявлений верное — не видно.
23
- - **Контракт различает короткое сообщение набора и полное сообщение записи.** Одно сообщение
24
- на оба случая делает выбор уровня невозможным: лишнее приезжает независимо от того, кто
25
- спрашивает.
26
- - **Открытая запись читается своим запросом по идентификатору.** Взятая из уже загруженного
27
- набора, она открывается по ссылке только после того, как загрузится весь набор.