@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.
- package/README.md +194 -30
- package/assets/agents/business-analyst.md +74 -0
- package/assets/agents/project-manager.md +70 -0
- package/assets/agents/qa-engineer.md +72 -0
- package/assets/agents/skill-curator.md +110 -0
- package/assets/agents/spec-critic.md +44 -0
- package/assets/agents/spec-writer.md +50 -0
- package/assets/checks/board.github.mjs +329 -0
- package/assets/checks/check-board.github.mjs +181 -0
- package/assets/checks/check-doc-paths.mjs +163 -0
- package/assets/checks/check-dupes.mjs +277 -0
- package/assets/checks/check-lib-layers.mjs +573 -0
- package/assets/checks/check-reuse.mjs +208 -0
- package/assets/checks/check-schema-drift.mjs +186 -0
- package/assets/checks/check-specs.mjs +1086 -0
- package/assets/checks/check-styles.mjs +109 -0
- package/assets/checks/rt-kit-checks.config.mjs +134 -0
- package/assets/checks/task-new.github.mjs +198 -0
- package/assets/commands/skill-curator.md +70 -0
- package/assets/defaults/gate-map.sh +106 -0
- package/assets/defaults/project.sh +204 -0
- package/assets/hooks/browser-device-id.sh +0 -0
- package/assets/hooks/browser-guard-device-id.sh +2 -1
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +2 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
- package/assets/hooks/browser-guard-require-select.sh +2 -1
- package/assets/hooks/commit-msg.sh +1 -1
- package/assets/hooks/constitution-index.sh +5 -4
- package/assets/hooks/dev-server-guard.sh +8 -6
- package/assets/hooks/docs-guard.sh +223 -37
- package/assets/hooks/git-guard-delivery.sh +171 -31
- package/assets/hooks/git-guard-main.sh +1 -0
- package/assets/hooks/git-guard-push-tests.sh +34 -13
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/grill-gate.sh +96 -0
- package/assets/hooks/lint-after-edit.sh +155 -30
- package/assets/hooks/qa-dataid-guard.sh +72 -32
- package/assets/hooks/reuse-first-guard.sh +105 -34
- package/assets/hooks/skill-gate-rearm.sh +1 -0
- package/assets/hooks/skill-gate.sh +75 -15
- package/assets/hooks/skill-loaded.sh +1 -0
- package/assets/hooks/sql-guard.sh +606 -56
- package/assets/hooks/task-context-load.sh +100 -0
- package/assets/hooks/task-flow-guard.sh +118 -0
- package/assets/laws/{access.md → application/access.md} +1 -4
- package/assets/laws/{locales.md → application/locales.md} +1 -3
- package/assets/laws/application/money.md +41 -0
- package/assets/laws/application/ownership.md +32 -0
- package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
- package/assets/laws/code-structure.md +7 -6
- package/assets/laws/delivery.md +53 -3
- package/assets/laws/entity-editing.md +49 -55
- package/assets/laws/entity-models.md +4 -14
- package/assets/laws/frontend-application.md +5 -5
- package/assets/laws/lib-imports.md +14 -1
- package/assets/laws/lists.md +33 -0
- package/assets/laws/navigation.md +40 -0
- package/assets/laws/project-documentation.md +27 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +30 -1
- package/assets/laws/work-conduct.md +59 -0
- package/assets/patterns/admin-lists-screen.md +131 -0
- package/assets/patterns/admin-nav-item.md +71 -0
- package/assets/patterns/angular-patterns-state.md +29 -22
- package/assets/patterns/api-layer-pair.md +40 -30
- package/assets/patterns/browser-verification-measure.md +41 -38
- package/assets/patterns/browser-verification-stand.md +106 -42
- package/assets/patterns/component-structure-new.md +33 -32
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +65 -28
- package/assets/patterns/doc-style-write.md +36 -33
- package/assets/patterns/entity-aside.md +136 -0
- package/assets/patterns/entity-models-new.md +124 -0
- package/assets/patterns/entity-store.md +91 -0
- package/assets/patterns/git-workflow-commit.azure.md +259 -0
- package/assets/patterns/git-workflow-commit.github.md +337 -0
- package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
- package/assets/patterns/git-workflow-merge.md +42 -25
- package/assets/patterns/git-workflow-migration.md +61 -31
- package/assets/patterns/git-workflow-restart.md +20 -20
- package/assets/patterns/lib-layers-move.md +50 -32
- package/assets/patterns/lib-layers-new.md +41 -29
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +35 -33
- package/assets/patterns/platform-access-di.md +39 -25
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +22 -22
- package/assets/patterns/seo-page.md +52 -40
- package/assets/patterns/seo-verify.md +48 -29
- package/assets/patterns/shared-code-new.md +37 -31
- package/assets/patterns/spec-driven-domain.md +60 -37
- package/assets/patterns/spec-driven-rule.md +55 -40
- package/assets/patterns/styling-bem-component.md +43 -32
- package/assets/patterns/styling-bem-layout.md +30 -24
- package/assets/patterns/task-flow-close.md +154 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +129 -0
- package/assets/patterns/testing-e2e.md +53 -51
- package/assets/patterns/testing-unit.md +70 -46
- package/assets/patterns/translations-key.md +32 -19
- package/assets/patterns/ts-procedure.md +24 -25
- package/assets/rules/angular-patterns.md +50 -27
- package/assets/rules/api-layer.md +46 -28
- package/assets/rules/browser-verification.md +67 -48
- package/assets/rules/component-structure.md +43 -27
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +95 -39
- package/assets/rules/entity-conventions.md +78 -0
- package/assets/rules/entity-models.md +70 -0
- package/assets/rules/git-workflow.azure.md +116 -0
- package/assets/rules/git-workflow.github.md +123 -0
- package/assets/rules/git-workflow.gitlab.md +113 -0
- package/assets/rules/lib-layers.md +56 -30
- package/assets/rules/lists.md +73 -0
- package/assets/rules/navigation.md +78 -0
- package/assets/rules/ownership-scope.md +63 -0
- package/assets/rules/permissions.md +43 -25
- package/assets/rules/platform-access.md +57 -29
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +57 -43
- package/assets/rules/seo.md +51 -30
- package/assets/rules/shared-code.md +51 -26
- package/assets/rules/spec-driven.md +107 -51
- package/assets/rules/styling-bem.md +54 -39
- package/assets/rules/task-flow.md +150 -0
- package/assets/rules/testing.md +78 -47
- package/assets/rules/translations.md +48 -31
- package/assets/rules/typescript-conventions.md +57 -27
- package/assets/skills/agent-kit.md +85 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +23 -15
- package/assets/templates/implementation.md +14 -8
- package/assets/templates/pattern.md +1 -1
- package/assets/templates/project.sh +32 -19
- package/assets/templates/rule.md +2 -2
- package/assets/variants.json +20 -0
- package/assets/workflows/feature.js +134 -0
- package/assets/workflows/plan.js +150 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +78 -5
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +5 -0
- package/bin/prompt.d.ts.map +1 -1
- package/bin/prompt.js +19 -7
- package/bin/prompt.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +8 -3
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +13 -3
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +52 -5
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +104 -16
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +22 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +202 -14
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +5 -1
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +29 -2
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +26 -9
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +41 -15
- package/lib/config.js.map +1 -1
- package/lib/freshness.d.ts +14 -0
- package/lib/freshness.d.ts.map +1 -0
- package/lib/freshness.js +116 -0
- package/lib/freshness.js.map +1 -0
- package/lib/hooks-map.d.ts +27 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +77 -0
- package/lib/hooks-map.js.map +1 -0
- package/lib/integrity.d.ts +36 -0
- package/lib/integrity.d.ts.map +1 -0
- package/lib/integrity.js +44 -0
- package/lib/integrity.js.map +1 -0
- package/lib/picker.d.ts +11 -1
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +44 -6
- package/lib/picker.js.map +1 -1
- package/lib/sync.d.ts +26 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +59 -4
- package/lib/sync.js.map +1 -1
- package/lib/variants.d.ts +44 -0
- package/lib/variants.d.ts.map +1 -0
- package/lib/variants.js +82 -0
- package/lib/variants.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.5.0.tgz +0 -0
- package/assets/laws/admin-lists.md +0 -35
- package/assets/laws/admin-navigation.md +0 -38
- package/assets/patterns/git-workflow-commit.md +0 -175
- package/assets/rules/git-workflow.md +0 -106
- 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
|
код читают чаще, чем пишут: имя, по которому не видно рода объявления, и тип, объявленный
|
|
@@ -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
|
-
открывается не тем, чего от него ждали, — и так на каждом чтении заново.
|
package/assets/laws/delivery.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
набора, она открывается по ссылке только после того, как загрузится весь набор.
|