Скрипт docs/scripts/sync-statements.sh (--check 0/10/20/2, --in-place, --dry-run, обычный режим с worktree/issue/PR), помощник statements_check.py, тест на синтетических репозиториях; три bounded PR подключения копии в arm-core, tabular-parser, scan-gateway. Решения P2-1…P2-21, открытые O-1…O-4; закрыты O-1…O-4 плана 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
69 KiB
План 2 — sync-statements и statements-check: копия пакета arm_statements в хостах. План реализации
Для исполнителей-агентов: обязательный навык — superpowers:subagent-driven-development (в плане 9 задач: по
~/.claude/CLAUDE.md— SDD, контроллер — фоновый сабагент на модели sonnet; исполнители задач — sonnet: готового кода в плане нет, есть интерфейсы, сценарии тестов и правила спеки; финальное ревью каждой ветки — opus). Шаги размечены чекбоксами (- [ ]).
Goal: скрипт docs/scripts/sync-statements.sh раскладывает пакет arm_statements из репозитория arm-statements
копией в api/arm_statements/ хостов (arm-core, tabular-parser, scan-gateway) и проверяет свежесть копии (--check:
коды 0 / 10 / 20 / 2); три хоста подключают копию (линтеры, testpaths, statements-check в make check-full).
Architecture: по образцу scripts/sync-web.sh: bash-скрипт ведёт git-часть (источник, worktree, issue, PR), а
разбор pyproject.toml и YAML делает маленький помощник scripts/statements_check.py (stdlib tomllib + PyYAML).
Точка синхронизации не ищется по истории, как у sync-web, — она записана в api/arm_statements/VERSION: копия
сравнивается с деревом arm_statements/ коммита из VERSION (без самого VERSION); отличие — прямая правка (20).
Три режима раскладки: обычный (новый worktree хоста, issue, проверки, коммит, PR), --dry-run (worktree и раскладка без
issue и PR) и --in-place (раскладка в рабочее дерево существующего worktree без git-операций — для подключения хоста и
для PR фичи, которому нужна новая версия пакета). Тест — scripts/test_sync_statements.sh на синтетических
репозиториях без сети, tea и Docker (как test_sync_web.sh).
Tech Stack: bash (совместимость с bash 3.2 macOS), git, BSD/GNU diff/tar, Python 3.11 (tomllib), PyYAML
6.0.3, GNU make, tea (Gitea), uv (окружения хостов).
Spec: openspec/changes/unified-statement-normalization/ (arm-ka-docs, ветка feat/116-unified-normalization):
specs/statement-normalization/spec.md — требования «Пакет общего кода arm_statements и его копии» и «sync-statements
— копии пакета в потребителях» со сценариями; specs/quality-checks/spec.md — make check и make check-full;
proposal.md — «Порядок выкладки»; dev/checks.md — «Копия общего кода arm_statements и sync-statements»;
context/03-architecture.md — «Общий код нормализации ББ/ОФР». Справка — design.md S-27, S-30, S-58, S-70, Р-1.
План пакета — plans/2026-10-18-plan-1-arm-statements.md (структура репозитория, VERSION, P1-1…P1-3, O-1…O-4).
Предусловия
- П-1 (docs, задачи 1–5). Контроллер: issue в
RSHB/arm-ka-docs(tea issues create --login home --repo RSHB/arm-ka-docs, заголовок «Скрипт sync-statements: копия пакета arm_statements в хостах (#116)», ссылка на #116 и этот план); worktree/Users/andrey.epifancev/projects/awp-ca/.wt/sync-statements/docsна временной ветке отorigin/main, затем веткаfeat/<issue>-sync-statements; в worktree —codegraph init;make venv.$D= этот путь. Ветка неfeat/116-unified-normalization: поproposal.md(шаг 1) PR скрипта сливается раньше docs PR #117. - П-2 (задача 6). Правки спеки,
design.mdи раздела «Копия общего кода»dev/checks.md— в веткеfeat/116-unified-normalization(worktree/Users/andrey.epifancev/projects/awp-ca/.wt/normalization/docs), пока PR #117 открыт; если он уже слит — в ветке П-1 до её PR. - П-3 (хосты, задачи 7–9). Старт — после слияния PR плана 1 в
RSHB/arm-statementsmain(вorigin/mainестьarm_statements/иpyproject.toml) и PR задач 1–5 в docsmain. Чекаутarm-statements—/Users/andrey.epifancev/projects/awp-ca/arm-statements(П-2 плана 1). Зеркальный workspace/Users/andrey.epifancev/projects/awp-ca/.wt/statements-adopt/($W): worktree хостов$W/arm-core,$W/tabular-parser,$W/scan-gateway(каждый — своя issue и веткаfeat/<issue>-arm-statements-copyотorigin/main),$W/docs— отсоединённый worktree arm-ka-docs наorigin/main(в нём ничего не коммитится), симлинкиfixtures,reference-old-arm,CLAUDE.md,.claudeна основной workspace (context/00-workspace-layout.md). В каждом worktree хоста —.bounded(bounded-задача: без OpenSpec change и плана хоста) иcodegraph init. - П-4. Docs PR #117 для задач 7–9 не обязателен: без
contracts/statement-code-orders.yamlв docsstatements-checkдаёт10(P2-9), это вписывается в описание PR.
Global Constraints
- Копия лежит в
api/arm_statements/хоста; маркер —api/arm_statements/VERSION: полный 40-символьный хэш коммитаarm-statementsи перевод строки; пишет его только скрипт. В источнике файлаVERSIONнет, в сравнении он не участвует. - Коды
--check— дословно спека:0— копия равна деревуarm_statements/коммита изVERSION, это коммит--ref, и копия перечня кодов равнаdocs/contracts/statement-code-orders.yaml;10— копия равна дереву коммита изVERSION, но в--refесть более новый, или копия перечня отличается от файла docs (называется файл);20— прямая правка копии,VERSIONнет в истории источника, зависимость пакета ([project].dependenciesегоpyproject.toml) отсутствует в зависимостях хоста, или (толькоarm-core) кодapi/rules/statement_lines.yamlотсутствует в перечне своей редакции копии пакета;2— нет checkout источника или хоста, неизвестный аргумент. Файлы и коды расхождения печатаются. Если причин несколько — печатаются все, код — наибольший по тяжести (20>10>0). - Источник по умолчанию —
arm-statementsрядом с workspace: кандидаты$HERE/../../../../arm-statements(скрипт в.wt/<задача>/docs), затем$HERE/../../arm-statements; ссылка —origin/main. Хост —--host arm-core|tabular-parser|scan-gateway(каталог$WS/<имя>) или--host-dir <dir>. Docs — каталог, где лежит скрипт ($HERE/..), или--docs <dir>. - Gitea-репозитории хостов:
arm-core→RSHB/arm-core,tabular-parser→RSHB/arm-tabular-parser,scan-gateway→RSHB/arm-scan-gateway.tea— флаги--login home --repo …после подкоманды (arm-ka-docs#110). statements-checkхоста входит вmake check-fullи не входит вmake checkи пайплайн платформы.- Скрипты совместимы с bash 3.2 (macOS): без
mapfile,declare -A,${var,,};set -euo pipefail; BSD и GNUdiff/tar/sed(безsed -i). - Конфиг линтеров пакета в копию не кладётся (P1-1): к копии применяется конфиг хоста. Правки копии под хост запрещены.
- Пины инструментов во всех хостах — как в пакете:
ruff==0.16.10,mypy==2.4.0,types-PyYAML==6.0.12.20260906. Зависимость пакетаPyYAML==6.0.3уже есть вapi/requirements.txtвсех трёх хостов (проверено 18.10.2026);api/requirements.txtхостов («платформенные зависимости, не меняем») этим планом не меняется. - Язык сообщений скриптов, комментариев, коммитов, issue и PR — русский. Коммиты заканчиваются строками
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>иClaude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W; PR — строкой🤖 Generated with [Claude Code](https://claude.com/claude-code). Слияние — только по команде человека. - Находки и решения несут маркеры (
T<n>.<k>,C-n/I-n/M-n,R-n,O-n); решения этого плана —P2-n(раздел в конце); задача 6 переносит их вdesign.md.
Review Focus
- Кэши в копии после
make checkхоста —__pycache__/,*.pyc,.pytest_cache/,.mypy_cache/,.ruff_cache/,.DS_Storeвнутриapi/arm_statements/не делают копию «прямой правкой»:--check=0. Тест — задача 2. .gitignoreхоста прячет файл пакета — у хостов шаблонный.gitignore(lib/,build/,out,dist,coverage,logs,*.spec,*.log…): разложенный файл есть на диске, но в коммит не попадёт, и в чужом клоне--checkдаст20. Раскладка (--in-place, обычный режим) завершается кодом 1 и называет игнорируемые файлы. Тест — задача 3.- Добавленный и удалённый файл копии, а не только изменённый, —
20с именем файла. Тест — задача 2. - Запуск из
Makefileхоста с--host-dir .из worktree.wt/<задача>/<хост>: относительный путь, имя хоста поorigin(arm-tabular-parser→tabular-parser), проверка словаря имён — только уarm-core(у scan-gateway тоже естьapi/rules/statement_lines.yaml, его не проверять). Тест — задача 2. VERSIONс ветки фичиarm-statements(коммит есть в источнике, но не предок--ref) —20«чужая версия», а не10. Тест — задача 2.
Структура файлов
| Файл | Репозиторий | Ответственность |
|---|---|---|
scripts/statements_check.py |
docs | помощник: зависимости пакета против requirements.txt хоста; коды словаря имён против перечня |
scripts/sync-statements.sh |
docs | разбор аргументов, поиск источника и хоста, --check, раскладка (--in-place, --dry-run, обычный режим) |
scripts/test_sync_statements.sh |
docs | сценарии на синтетических репозиториях |
Makefile |
docs | venv (+ PyYAML), test-sync-statements (в check), sync-statements, statements-check |
dev/checks.md |
docs | таблица «Проверки этого репозитория» (ветка П-1); раздел «Копия общего кода» (задача 6) |
api/arm_statements/**, api/pyproject.toml, api/pytest.ini, api/requirements-dev.txt, Makefile, CLAUDE.md |
хосты | подключение копии (задачи 7–9) |
Task 1: Помощник statements_check.py и каркас теста
Files:
- Create:
$D/scripts/statements_check.py,$D/scripts/test_sync_statements.sh - Modify:
$D/Makefile(venv,PY, новая цельtest-sync-statements)
Interfaces:
-
Produces (
scripts/statements_check.py, только stdlib +yaml, Python ≥ 3.11):normalize_name(name: str) -> str— PEP 503:re.sub(r"[-_.]+", "-", name).lower();requirement_name(line: str) -> str | None— имя из строки требования по^[A-Za-z0-9][A-Za-z0-9._-]*(экстры[crypto], версии, маркеры отбрасываются); пустая строка, комментарий, строка с-в начале (-r,--index-url) —None;missing_dependencies(pyproject_text: str, requirements_text: str) -> tuple[list[str], list[str]]— (имена из[project].dependenciesпакета, которых нет среди имёнrequirements.txt; примечания «версии различаются: пакетPyYAML==6.0.3, хостPyYAML==6.0.4»); нет[project].dependencies— пустой список;codes_outside_orders(lines_doc: object, orders_doc: object) -> list[str]— для каждой записиeditions[] = {id, forms: {ФОРМА: [{code, name}]}}словаря имён: код, которого нет в объединении вариантовorders.editions[id][ФОРМА](full∪simplified) перечня; сообщениекод 1120 формы BALANCE редакции fsbu4 нет в перечне копии пакета; редакции или формы нет в перечне — сообщение по каждому её коду;- CLI
main(argv: list[str]) -> int:deps <pyproject.toml> <requirements.txt>иlines <statement_lines.yaml> <statement_code_orders.yaml>; печатает находки по строке, примечания — с префиксомпримечание:; код0— нет находок,20— есть,1— файл не читается или не той формы (сообщение об ошибке в stderr),2— неверные аргументы.
-
Produces (
Makefile):PY— без изменений смысла;venvставитjsonschema pyyaml; цельtest-sync-statements: STATEMENTS_PY=$(PY) scripts/test_sync_statements.sh. СтрокиPY(комментарий),venvи комментарий над ней — дословно как в веткеfeat/116-unified-normalization(иначе конфликт при слиянии #117): «# Если окружение создано (make venv) — берём его интерпретатор, иначе системный python3 (в нём должны быть jsonschema и PyYAML)», «# Зависимости скриптов проверки контракта — jsonschema и PyYAML (перечни кодов форм contracts/statement-code-orders.yaml)», «uv pip install --python $(VENV)/bin/python jsonschema pyyaml». -
Produces (
scripts/test_sync_statements.sh): каркас как уtest_sync_web.sh—WORK=$(mktemp -d)сtrap,check <0|1> <описание>,put <путь> [текст],commit_push <repo> <msg> <branch>; интерпретатор —PY=${STATEMENTS_PY:-python3}, в начале проверка"$PY" -c 'import tomllib, yaml'(нет —exit 1с подсказкойmake venv); раздел «помощник» в начале, следующие задачи дописывают свои разделы; в концеok: sync-statements.sh …. -
Step 1: Write the failing test — раздел «помощник» в
test_sync_statements.sh: файлы в$WORK/h/.Проверка Вход Ожидание deps: есть pyproject [project]\ndependencies = ["PyYAML==6.0.3"]; requirementsaiohttp==3.14.3\nPyYAML==6.0.3код 0, пустой вывод deps: регистр и _requirements pyyaml==6.0.3код 0 deps: нет requirements aiohttp==3.14.3\n# PyYAML==6.0.3\n-r other.txtкод 20, в выводе PyYAMLdeps: другая версия requirements PyYAML==6.0.4код 0, в выводе примечание:и6.0.4deps: без [project]pyproject [tool.ruff]\nline-length = 120код 0 deps: экстры хоста pyproject dependencies = ["PyJWT==2.8.0"], requirementsPyJWT[crypto]==2.8.0код 0 lines: все коды есть словарь: 66nBALANCE1110,1100;fsbu4BALANCE1110; перечень: обе редакцииBALANCE: {full: ["1110", "1100"], simplified: ["1100"]},INCOME: {full: ["2110"], simplified: ["2110"]}код 0 lines: код вне перечня в fsbu4BALANCEсловаря добавлен1120код 20, в выводе 1120иfsbu4lines: код только в simplifiedперечень fsbu4 BALANCE: {full: ["1110"], simplified: ["1100"]}, словарьfsbu41100код 0 битый YAML словарь editions: [код 1 аргументы без аргументов код 2 Синтетический перечень — в форме
contracts/statement-code-orders.yaml(version: 1,repeats,editions: {66n: {BALANCE: {full, simplified}, INCOME: …}, fsbu4: …}); словарь — в формеarm-core/api/rules/statement_lines.yaml(editions: - {id: 66n, forms: {BALANCE: [{code: "1110", name: "…"}]}}). Запуск —"$PY" "$HERE/statements_check.py" …. -
Step 2: Run test to verify it fails
Run:
cd $D && make venv && make test-sync-statementsExpected: FAIL —statements_check.pyне найден (строкиFAIL:), код 1. -
Step 3: Implement
scripts/statements_check.pyпо интерфейсу;tomllib.loads,yaml.safe_load; docstring модуля — назначение и ссылка на спекуstatement-normalization. ПравкаMakefile(П-1 ветки —origin/main:.PHONYдополнитьtest-sync-statements;checkпока не трогать). -
Step 4: Run test to verify it passes
Run:
cd $D && make test-sync-statementsExpected: все строкиok:, код 0. -
Step 5: Commit
git -C "$D" add scripts/statements_check.py scripts/test_sync_statements.sh Makefile
git -C "$D" commit -m "feat(scripts): помощник statements_check.py — зависимости пакета и коды словаря имён против перечня (#<issue>)"
Task 2: sync-statements.sh --check
Files:
- Create:
$D/scripts/sync-statements.sh - Modify:
$D/scripts/test_sync_statements.sh(раздел «--check»)
Interfaces:
- Consumes: CLI
statements_check.py deps|lines(задача 1). - Produces (
scripts/sync-statements.sh):- Аргументы:
--check,--in-place,--dry-run,--host <имя>,--host-dir <dir>,--ref <ref>(источник, по умолчаниюorigin/main),--host-ref <ref>(база worktree хоста, по умолчаниюorigin/main),--source <dir>,--docs <dir>,--core-dir <dir>(по умолчанию$WS/arm-core),--wt-root <dir>(по умолчанию$WS/.wt),-h.--checkважнее--dry-runи--in-placeпри любом порядке (как уsync-web.sh);--in-placeвместе с--dry-run— код 2; ни--host, ни--host-dir— код 2; неизвестное имя--host— код 2. - Шапка-комментарий в стиле
sync-web.sh: назначение, точка синхронизации (VERSION), режимы, коды,-hпечатает её. - Имя хоста:
--host; иначе поgit -C <host> remote get-url origin— последний компонент без.git:arm-core→arm-core,arm-tabular-parser→tabular-parser,arm-scan-gateway→scan-gateway; иначе имя каталога; не из трёх — код 2. - Интерпретатор помощника:
STATEMENTS_PY→$DOCS/.venv/bin/python→$HOST/api/.venv/bin/python→python3; первый, у которогоimport tomllib, yamlпроходит; ни одного — код 2 с подсказкойmake -C docs venv. git -C "$SRC" fetch -q origin; ошибка fetch — предупреждение «fetch не удался, сравниваю с локальным », работа продолжается.--refне разрешается в источнике — код 2.- Функция
evaluate <copy_dir> <requirements_file> <lines_file|""> <host_name>— печатает находки и выставляетEVAL_CODE(0/10/20); используется задачами 3–4 для проверки дерева после раскладки и дерева--host-ref. - Итоговая строка
--check(её ищут тесты иMakefileхоста):statements-check <хост>: 0 — копия синхронна с arm-statements@<ref> (<v7>);statements-check <хост>: 10 — ПРЕДУПРЕЖДЕНИЕ: <причины через «; »>;statements-check <хост>: 20 — расхождение, копию не принимать; код возврата = число в строке.
- Аргументы:
evaluate проверяет по порядку (печатает всё найденное):
| # | Условие | Сообщение (суть) | Код |
|---|---|---|---|
| 1 | нет каталога api/arm_statements/ |
«копии нет: разложите sync-statements.sh --in-place --host-dir <dir>» |
20 (остальное не проверяется) |
| 2 | нет VERSION, не 40 hex, коммита нет в источнике |
«VERSION … нет в истории arm-statements» (+ «сделайте fetch», если fetch не удался) | 20 (3–4 не проверяются) |
| 3 | коммит VERSION не предок --ref |
«чужая версия: не из истории » | 20 |
| 4 | файлы копии ≠ git archive <VERSION> arm_statements (извлечь во временный каталог; diff -rq с исключениями VERSION, __pycache__, *.pyc, .pytest_cache, .mypy_cache, .ruff_cache, .DS_Store) |
«прямая правка копии:» и по строке на файл — путь относительно arm_statements/ (изменён / только в копии / только в источнике) |
20 |
| 5 | statements_check.py deps <pyproject.toml коммита VERSION> <requirements_file> = 20 |
его вывод | 20 |
| 6 | хост arm-core и есть api/rules/statement_lines.yaml: statements_check.py lines <словарь> <копия>/rules/statement_code_orders.yaml = 20 |
его вывод | 20 |
| 7 | нет $DOCS/contracts/statement-code-orders.yaml или он не равен байтово <копия>/rules/statement_code_orders.yaml |
«перечень кодов в docs отличается от копии: contracts/statement-code-orders.yaml» (или «нет в docs») | 10 |
| 8 | VERSION ≠ git rev-parse <ref> |
«копия отстала: , — », список git log --format=' %h %s' <VERSION>..<ref> |
10 |
Код 1 помощника — код скрипта 1 с его сообщением. Режим --check читает рабочее дерево --host-dir (незакоммиченная
правка тоже 20); requirements_file — <host>/api/requirements.txt.
-
Step 1: Write the failing test — раздел «--check» в
test_sync_statements.sh.Подготовка:
arm-statements.git(bare) + клон$WORK/arm-statements, веткаmain:pyproject.toml([project],name = "arm-statements",dependencies = ["PyYAML==6.0.3"]),README.md,arm_statements/__init__.py,arm_statements/steps.py,arm_statements/rules/statement_code_orders.yaml(синтетический перечень задачи 1),arm_statements/tests/__init__.py,arm_statements/tests/test_steps.py; коммит S1, push.$WORK/docs/contracts/ statement-code-orders.yaml— байтовая копия перечня S1 (не git). Хосты — bare + клон:$WORK/arm-core(originarm-core.git;api/requirements.txtсPyYAML==6.0.3;api/rules/statement_lines.yaml— словарь задачи 1),$WORK/tabular-parser(originarm-tabular-parser.git),$WORK/scan-gateway(originarm-scan-gateway.git;api/rules/statement_lines.yamlс кодом9999вfsbu4). В каждый хост копия кладётся вручную в тесте:git -C "$SRC" archive S1 arm_statements | tar -x -C <host>/apiиVERSION= полный хэш S1; коммит, push.COMMON=(--source "$WORK/arm-statements" --docs "$WORK/docs" --wt-root "$WORK/wt" --core-dir "$WORK/arm-core").Проверка Действие Ожидание синхронно --check --host-dir $WORK/arm-core0, вывод содержит statements-check arm-core: 0имя по origin, словарь не у scan-gateway --check --host-dir $WORK/scan-gateway0, вывод содержит scan-gateway, нет9999--host-dir .из каталога хоста(cd $WORK/tabular-parser && "$SYNC" --check --host-dir . "${COMMON[@]}")0, statements-check tabular-parser: 0кэши в копии tabular-parser __pycache__/steps.cpython-311.pyc,tests/.pytest_cache/x,.mypy_cache/x,.DS_Store0 изменён файл дописать строку в api/arm_statements/steps.pyarm-core (без коммита)20, в выводе steps.pyипрямая правкадобавлен файл новый api/arm_statements/extra.py(правкуsteps.pyоткатить)20, в выводе extra.pyудалён файл удалить api/arm_statements/tests/test_steps.py20, в выводе test_steps.pyнет VERSIONудалить VERSION20 неизвестный VERSIONVERSION=0000…(40 нулей)20 чужая версия в источнике ветка feat/xс коммитом F (правкаsteps.py), push; копия arm-core = дерево F,VERSION= F20, в выводе чужая версиянет зависимости arm-core: api/requirements.txtбез PyYAML (копия = S1)20, в выводе PyYAMLкод словаря вне перечня arm-core: в fsbu4словаря112020, в выводе 1120иfsbu4перечень в docs новее $WORK/docs/contracts/statement-code-orders.yamlдописан# новый код10, в выводе contracts/statement-code-orders.yaml; затем вернуть файлперечня нет в docs --docs $WORK/docs-empty(пустой каталог)10 отстала в источнике коммит S2 (правка README.md), push10, в выводе копия отсталаи тема S2правка + отставание правка steps.pyarm-core при S220 (не 10) нет источника --source $WORK/nope2 нет хоста --host-dir $WORK/nope2 неизвестный аргумент --bogus2 нет --host/--host-dirтолько --check2 --in-place --dry-runоба флага 2 Каждая проверка после себя возвращает хосты к копии S1 (
git -C <host> checkout -- . && git -C <host> clean -fdq). Перед «отстала» и после неё порядок проверок не меняется: проверки с S2 идут последними. -
Step 2: Run test to verify it fails
Run:
cd $D && make test-sync-statementsExpected: FAIL — строкиFAIL:раздела «--check» (скрипта нет). -
Step 3: Implement разбор аргументов, поиск workspace/источника/хоста (как в
sync-web.sh:WSпо кандидатам$HERE/../../../..и$HERE/../.., где рядом естьarm-statements/.git), выбор интерпретатора,evaluateи режим--check. Режимы раскладки пока завершаютсяdie "режим не реализован" 1. -
Step 4: Run test to verify it passes
Run:
cd $D && make test-sync-statementsExpected: все строкиok:, код 0. -
Step 5: Commit
git -C "$D" add scripts/sync-statements.sh scripts/test_sync_statements.sh
git -C "$D" commit -m "feat(scripts): sync-statements.sh --check — свежесть копии arm_statements в хосте, коды 0/10/20/2 (#<issue>)"
Task 3: Раскладка --in-place
Files:
- Modify:
$D/scripts/sync-statements.sh,$D/scripts/test_sync_statements.sh(раздел «--in-place»)
Interfaces:
-
Consumes:
evaluate(задача 2). -
Produces: функция
lay_copy <host_root> <commit>— удаляетapi/arm_statements/целиком, извлекаетgit -C "$SRC" archive <commit> arm_statements | tar -x -C <host_root>/api, пишетVERSION(<полный хэш>\n); затем две защиты, любая — код 1 со списком:- игнорируемые файлы:
git -C <host_root> ls-files --others --ignored --exclude-standard -- api/arm_statementsпосле исключения кэшей (__pycache__,*.pyc,.pytest_cache,.mypy_cache,.ruff_cache) непуст — «файлы копии игнорируются .gitignore хоста, в коммит не попадут:»; - содержимое, на котором остановится перенос в контур (
prepare-contour-snapshot.sh, шаг 8):grep -rIn "/Users/\|/home/[a-z]\|git.aepif.ru\|uv venv\|uv pip"по копии — «копия сломает снимок для контура:».
- игнорируемые файлы:
-
Режим
--in-place(хост —--hostили--host-dir; рабочее дерево, без git-операций):- если копия есть —
evaluateпротив еёVERSION; код 20 по условиям 1–4 (правка, чужая версия) — отказ20, ничего не пишется; - зависимости
--ref(pyproject.tomlкоммита--ref) противapi/requirements.txtхоста и (толькоarm-core) словарь имён против перечня коммита--ref(git show <ref>:arm_statements/rules/statement_code_orders.yaml) — находка — отказ20, ничего не пишется; - копия уже на
--refи равна ему — «раскладывать нечего», переход к шагу 5; lay_copy;evaluateитога; печать итоговой строки как у--check; код возврата =EVAL_CODE. Подсказка в конце:закоммитьте api/arm_statements отдельным коммитом: git -C <host> add api/arm_statements.
- если копия есть —
-
Step 1: Write the failing test — раздел «--in-place». Новый хост без копии: bare
$WORK/fresh/arm-tabular-parser.git+ клон$WORK/fresh/tabular-parser(имя хоста выводится из origin —tabular-parser),api/requirements.txtсPyYAML==6.0.3, коммит, push; ниже он — «fresh».Проверка Действие Ожидание первичная раскладка хост без api/arm_statements/,--in-place --host-dir <fresh> --ref origin/main(S2)0; VERSION= полный хэш S2;diff -rкопии (безVERSION) сgit archive S2 arm_statementsпуст;git statusхоста не менялся внеapi/arm_statementsповтор ещё раз 0, в выводе раскладывать нечегообновление копия arm-core на S1, --in-place --host-dir $WORK/arm-core0, VERSION= S2отказ при правке в копии arm-core правка steps.py,--in-place20; steps.pyс правкой на месте,VERSIONне изменилсяотказ без зависимости api/requirements.txtfresh без PyYAML20; копия не тронута .gitignoreпрячет файлв источнике коммит S3: arm_statements/tests/golden/case.json; в fresh.gitignoreстрока*.json;--in-place1; в выводе case.jsonи.gitignoreсодержимое для контура в источнике коммит S4: строка # /Users/someoneвsteps.py;--in-placeв fresh без*.jsonв.gitignore1; в выводе /Users/Коммиты S3/S4 в источнике делаются на отдельной ветке
test-guardsи передаются через--ref origin/test-guards, чтобыorigin/main(S2) остался для задачи 4. -
Step 2: Run test to verify it fails
Run:
cd $D && make test-sync-statementsExpected: FAIL — строкиFAIL:раздела «--in-place». -
Step 3: Implement
lay_copyи режим--in-placeпо интерфейсу. -
Step 4: Run test to verify it passes
Run:
cd $D && make test-sync-statementsExpected: все строкиok:, код 0. -
Step 5: Commit
git -C "$D" add scripts/sync-statements.sh scripts/test_sync_statements.sh
git -C "$D" commit -m "feat(scripts): sync-statements.sh --in-place — раскладка копии в рабочее дерево хоста с защитой от .gitignore и путей для контура (#<issue>)"
Task 4: Обычный режим и --dry-run: worktree, issue, проверки хоста, PR
Files:
- Modify:
$D/scripts/sync-statements.sh,$D/scripts/test_sync_statements.sh(раздел «синхронизация»)
Interfaces:
-
Consumes:
evaluate(задача 2),lay_copy(задача 3). -
Produces — обычный режим (без
--check/--in-place), по шагамsync-web.sh:fetchисточника и хоста;TARGET=git -C "$SRC" rev-parse <ref>,T7= 7 символов.- Состояние хоста на
--host-ref: нет<host-ref>:api/arm_statements— код 2 «копии нет: подключение хоста — вручную,--in-placeв worktree задачи подключения (dev/checks.md)». Иначе — деревоgit archive <host-ref> apiво временный каталог иevaluateпо нему (requirements— оттуда же);20— отказ20с находками, worktree не создаётся. - Зависимости и словарь имён против
TARGET(как шаг 2--in-place) — находка — отказ20. VERSIONна--host-ref=TARGET— «копия уже на , раскладывать нечего», код 0 (предупреждение о перечне кодов, если есть).- «Сначала ядро»: для хоста ≠
arm-core—VERSIONв<core-dir>@origin/main; его нет, или он ≠TARGETиTARGETне его предок — предупреждениеядро (arm-core@origin/main) ещё не на <T7>: по порядку выкладки сначала ядро(не блокирует; строка уходит и в описание PR). - Worktree
$WT_ROOT/statements-sync-<T7>/<хост>наtmp/statements-sync-<T7>от--host-ref; существует worktree или ветка — код 2;touch .bounded. - Не
--dry-run: issue (tea issues create --login home --repo <Gitea-репозиторий> --title "arm_statements: копия пакета arm-statements@<T7>" --description …— версия копии было/стало, коммиты<VERSION>..<TARGET>, «bounded, выполненаdocs/scripts/sync-statements.sh»); ветка переименовывается вfeat/<issue>-statements-sync-<T7>. lay_copy "$WT" "$TARGET";evaluateпо worktree — допустимы0и10только по перечню кодов (условие 7), иначе код 1, worktree оставлен.--dry-run: печать «dry-run: issue, проверки, коммит и PR не выполнялись» и команды удаления worktree и ветки; код 0.- Проверки хоста:
make -C "$WT" venvиmake -C "$WT" check; ошибка — хвост вывода (40 строк), worktree оставлен, код 1. - Коммит
chore(arm_statements): копия arm-statements@<T7> (#<issue>), тело — диапазон, коммиты источника, итогstatements-check, трейлерCo-Authored-By: sync-statements.sh <noreply@aepif.ru>;push -u; PR (tea pulls create … --head <ветка> --base <host-ref без origin/>), тело: «Закрывает #», было/стало, коммиты, итогmake checkиstatements-check(предупреждение — дословно), предупреждение «сначала ядро» (если было), раздел## Rulings I made(R-1 — копия не правится, перенос проревьюенного в arm-statements кода; R-2 — bounded без OpenSpec change), строка🤖 Generated with [Claude Code](https://claude.com/claude-code). - Итог: коммит, ветка, issue, PR, «исполнителю: ревью диапазона не нужно — код проревьюен в arm-statements; merge и «закрывай» по команде человека».
-
Step 1: Write the failing test — раздел «синхронизация» (хосты arm-core и tabular-parser с копией S1,
origin/mainисточника = S2).Проверка Действие Ожидание dry-run создаёт worktree --dry-run --host-dir $WORK/arm-core0; есть $WORK/wt/statements-sync-<S2_7>/arm-core; веткаtmp/statements-sync-<S2_7>; есть.bounded;VERSION= S2;diff -rкопии сgit archive S2 arm_statementsпустdry-run не трогает хост после него git -C $WORK/arm-core status --porcelainпуст,VERSIONхоста = S1повтор dry-run ещё раз ≠ 0, в выводе уже существует; затем worktree и ветка удаляютсясначала ядро --dry-run --host-dir $WORK/tabular-parser(у arm-coreorigin/mainкопия S1)0, в выводе сначала ядро; worktree удалитьуже синхронно копия arm-core на S2 закоммичена и запушена, --dry-run0, раскладывать нечего, worktree не созданотказ при прямой правке на origin/maintabular-parser: правка api/arm_statements/steps.pyзакоммичена и запушена,--dry-run20, в выводе steps.py, worktree не созданхост без копии fresh-хост без api/arm_statementsнаorigin/main,--dry-run2, в выводе --in-placetea: флаги после подкоманды`grep -nE '(^ [^[:alnum:]_-])tea:space:+-' sync-statements.sh` -
Step 2: Run test to verify it fails
Run:
cd $D && make test-sync-statementsExpected: FAIL — строкиFAIL:раздела «синхронизация». -
Step 3: Implement обычный режим и
--dry-runпо интерфейсу (teaиmakeв тесте не вызываются — тест гоняет только--dry-run, какtest_sync_web.sh). -
Step 4: Run test to verify it passes
Run:
cd $D && make test-sync-statementsExpected: все строкиok:, код 0. -
Step 5: Commit
git -C "$D" add scripts/sync-statements.sh scripts/test_sync_statements.sh
git -C "$D" commit -m "feat(scripts): sync-statements.sh — worktree хоста, issue, make check и PR; --dry-run; предупреждение «сначала ядро» (#<issue>)"
Task 5: Цели make docs, dev/checks.md, PR docs
Files:
- Modify:
$D/Makefile,$D/dev/checks.md(только таблица раздела «Проверки этого репозитория (docs)»)
Interfaces:
-
Produces (
Makefile,.PHONYдополнен):sync-statements: STATEMENTS_PY=$(PY) scripts/sync-statements.sh $(SYNC_ARGS)— комментарий: «Копия пакета arm_statements в хост одной командой (worktree, issue, PR); SYNC_ARGS="--host arm-core [--check|--dry-run|--in-place]"»;statements-check—--checkпо трём хостам рядом (arm-core tabular-parser scan-gateway): по строке итога на хост, затем сводка;0и10— зелёные, любой другой код — цель завершается кодом 1 после проверки всех трёх. Комментарий: «обзор свежести копий во всех хостах; в check не входит — читает соседние репозитории»;check: validate-contracts validate-openspec test-snapshot test-sync-web test-sync-statements.
-
Produces (
dev/checks.md): строкаmake checkтаблицы — «все проверки ниже иtest-snapshot,test-sync-web,test-sync-statements»; новые строки после неё:make test-sync-statements(сценарииsync-statements.shна синтетических репозиториях: коды--check,--in-place,--dry-run, отказ при прямой правке),make sync-statements(SYNC_ARGSкак выше),make statements-check(обзор по трём хостам). Строкиmake venvиmake validate-contractsне трогать (их меняет PR #117 — без конфликта). -
Step 1: Проверить, что
statements-checkdocs пока красный — до подключения хостов копий нет.Run:
cd $D && make statements-checkExpected (после реализации Step 2): три строкиstatements-check <хост>: 20 — …с причинойкопии нет, цель — код 2 отmake. До Step 2 —No rule to make target. -
Step 2: Implement цели
Makefileи строки таблицыdev/checks.md. -
Step 3: Run checks
Run:
cd $D && make check && make statements-check; echo rc=$?Expected:make check— все цели зелёные (в т. ч.ok: sync-statements.sh …);statements-check— три строки «копии нет»,rc=2. -
Step 4: Commit
git -C "$D" add Makefile dev/checks.md
git -C "$D" commit -m "feat(make): sync-statements, statements-check и test-sync-statements в check (#<issue>)"
- Step 5: Финальное ревью ветки (opus), PR —
git -C "$D" fetch && git -C "$D" rebase origin/main,make check,tea pulls create --login home --repo RSHB/arm-ka-docs --head feat/<issue>-sync-statements --base main; описание: «Закрывает #», что делает скрипт, коды, итогmake check, решенияP2-1…P2-21в разделе## Rulings I made, таблица находок с маркерами и статусами.
Task 6: Спека, design.md и раздел «Копия общего кода» (ветка feat/116-unified-normalization)
Files:
- Modify:
/Users/andrey.epifancev/projects/awp-ca/.wt/normalization/docs/openspec/changes/unified-statement-normalization/specs/statement-normalization/spec.md - Modify:
…/unified-statement-normalization/design.md(раздел## Decisions made without discussion) - Modify:
/Users/andrey.epifancev/projects/awp-ca/.wt/normalization/docs/dev/checks.md(раздел «Копия общего кодаarm_statementsиsync-statements», абзац под таблицей «Составmake check-full») - Modify:
…/unified-statement-normalization/proposal.md(«Порядок выкладки», шаг 1)
Interfaces:
-
Consumes: номера issue и PR docs (задача 5).
-
Step 1: Спека — в требовании «
sync-statements— копии пакета в потребителях» после предложения «Без--checkскрипт раскладывает…» добавить: «С--in-placeскрипт SHALL раскладывать копию коммита--refв рабочее дерево указанного хоста (существующий worktree) без issue, коммита и PR — для подключения хоста и для PR фичи, которой нужна новая версия пакета; при прямой правке копии или недостающей зависимости — отказ с кодом20, ничего не пишется.» и сценарий:#### Scenario: Копия в PR фичи - **WHEN** PR scan-gateway нужна версия пакета новее, чем в `main` хоста - **THEN** `sync-statements.sh --in-place --host-dir .` в worktree фичи раскладывает копию и пишет `VERSION`, копия коммитится отдельным коммитом этого PR; `--check` после раскладки — `0` -
Step 2:
dev/checks.md— в разделе «Копия общего кода…»: снять «Пока не реализовано: …», вместо него — «Скрипт и цели docs — arm-ka-docs#;statements-checkсервисов появляется с PR подключения копии в каждом хосте (план 2, задачи 7–9) — до шага 3 выкладки»; в пункт «Скрипт» добавить--in-place(как в спеке),--dry-run, обзорmake -C docs statements-check; в пункт «Где копия» — что конфиг линтеров пакета в копию не кладётся, а хост держитarm_statementsв строгом блокеmypyи вtestpaths(P2-1, P2-3). Абзац «statements-checkпоявляется в сервисе вместе с копией… (шаги 3 и 5 выкладки)» — заменить на «вместе с копией пакета (PR подключения хоста)». -
Step 3:
proposal.md— шаг 1: «arm-statements— пакет с тестами;sync-statementsв docs; копия пакета подключается к хостам (линтеры,testpaths,statements-check) — код сервисов её ещё не вызывает». -
Step 4:
design.md— решенияP2-1…P2-21раздела ниже с датой 18.10.2026 (формат раздела — как у решений P1-n: решение, обоснование, цена). -
Step 5: Проверить —
cd /Users/andrey.epifancev/projects/awp-ca/.wt/normalization/docs && openspec validate unified-statement-normalization --strict—valid. -
Step 6: Commit и push
G=/Users/andrey.epifancev/projects/awp-ca/.wt/normalization/docs
git -C "$G" add openspec/changes/unified-statement-normalization dev/checks.md
git -C "$G" commit -m "openspec(#116): sync-statements — режим --in-place, подключение копии к хостам, решения P2-n"
git -C "$G" pull --rebase && git -C "$G" push
Task 7: arm-core — подключение копии arm_statements
Files (worktree $W/arm-core, ветка feat/<issue>-arm-statements-copy, issue «Подключение копии пакета
arm_statements (arm-ka-docs#116, план 2)»):
- Create:
api/arm_statements/**(скриптом, руками не править) - Modify:
Makefile,api/pyproject.toml,api/pytest.ini,api/requirements-dev.txt,CLAUDE.md
Interfaces:
- Consumes:
../docs/scripts/sync-statements.sh(задачи 2–4, из$W/docs= docsorigin/main). - Produces для планов 3 (часть Б) и далее:
import arm_statementsизapi/;mypy src scripts arm_statements; тесты пакета вmake check; цельstatements-checkвcheck-full.
Правки (точные значения):
| Файл | Правка |
|---|---|
Makefile |
lint: $(BIN)/mypy src scripts arm_statements; новые ARM_KA_DOCS ?= ../docs и цель statements-check (рецепт ниже); check-full: check statements-check dbml-check check-infra test-integration e2e docker-build web-check; .PHONY + statements-check |
api/pyproject.toml |
[tool.ruff.lint.isort] known-first-party = ["src", "scripts", "arm_statements"]; в строгий блок [[tool.mypy.overrides]] module = ["src.models.*", "src.domain.*", "arm_statements", "arm_statements.*"] + комментарий «копия общего кода (sync-statements, не править) — в строгом режиме, как в репозитории arm-statements» |
api/pytest.ini |
testpaths = строки tests и arm_statements/tests |
api/requirements-dev.txt |
ruff==0.16.10, mypy==2.4.0, types-PyYAML==6.0.12.20260906 вместо строк с >=; комментарий «ruff, mypy, types-PyYAML — те же версии, что в arm-statements (копия пакета проверяется конфигом хоста)» |
CLAUDE.md |
в «Правила репозитория»: «api/arm_statements/ — копия пакета arm-statements, напрямую не править: обновление — ../docs/scripts/sync-statements.sh (--in-place в PR фичи), свежесть — make statements-check (в check-full)» |
Рецепт statements-check (одинаковый в трёх хостах):
# Свежесть копии api/arm_statements/ (../docs/dev/checks.md): 0 — синхронна; 10 — отстала или перечень кодов в docs
# новее (цель зелёная, предупреждение вписать в описание PR); 20 и 2 — красная. Читает ../docs и ../arm-statements,
# поэтому только в check-full, не в check
ARM_KA_DOCS ?= ../docs
statements-check:
@rc=0; $(ARM_KA_DOCS)/scripts/sync-statements.sh --check --host-dir . || rc=$$?; \
if [ $$rc -eq 10 ]; then echo "statements-check: ПРЕДУПРЕЖДЕНИЕ — впишите в описание PR"; \
elif [ $$rc -ne 0 ]; then exit $$rc; fi
- Step 1: Цель без копии — красная. Добавить в
MakefileтолькоARM_KA_DOCS,statements-checkи.PHONY. Run:make -C $W/arm-core statements-check; echo rc=$?Expected:statements-check arm-core: 20 — …, «копии нет»,rc=2(код make). - Step 2: Разложить копию.
Run:
cd $W/arm-core && ../docs/scripts/sync-statements.sh --in-place --host-dir .Expected:statements-check arm-core: 0(или10только с причиной «перечень кодов в docs…», пока docs PR #117 не слит);api/arm_statements/VERSION=git -C /Users/andrey.epifancev/projects/awp-ca/arm-statements rev-parse origin/main. - Step 3: Правки таблицы выше (кроме уже сделанного в Step 1).
- Step 4: Проверки.
Run:
make -C $W/arm-core venv checkExpected: зелёный;mypy src scripts arm_statementsбез ошибок. Run:cd $W/arm-core/api && .venv/bin/pytest --collect-only -q arm_statements/tests | tail -1иcd /Users/andrey.epifancev/projects/awp-ca/arm-statements && make venv >/dev/null && .venv/bin/pytest --collect-only -q | tail -1Expected: одинаковое числоtests collected. Run:make -C $W/arm-core statements-checkExpected: код 0, строка0или10(только перечень кодов). Run:make -C $W/arm-core docker-build && docker run --rm arm-core python -c "from arm_statements import default_rules, statements_version; default_rules(); print(statements_version())"Expected: печатается хэш изapi/arm_statements/VERSION(словариrules/*.yamlв образе). - Step 5:
make check-full(make upпри необходимости). Expected: зелёный; итог и предупреждениеstatements-check(если было) — в описание PR. - Step 6: Commit (два коммита) и PR.
git -C "$W/arm-core" add api/arm_statements
git -C "$W/arm-core" commit -m "chore(arm_statements): копия пакета arm-statements@<v7> (#<issue>)"
git -C "$W/arm-core" add Makefile api/pyproject.toml api/pytest.ini api/requirements-dev.txt CLAUDE.md
git -C "$W/arm-core" commit -m "build: копия arm_statements в линтерах и тестах, statements-check в check-full (#<issue>)"
git -C "$W/arm-core" push -u origin feat/<issue>-arm-statements-copy
tea pulls create --login home --repo RSHB/arm-core --head feat/<issue>-arm-statements-copy --base main --title "Подключение копии пакета arm_statements (#<issue>)" --description "…"
Описание PR: что подключено, что код сервиса пакет ещё не вызывает (это план 3, часть Б), итог check-full,
## Rulings I made (P2-1…P2-4, P2-20), строка Claude Code.
Task 8: tabular-parser — подключение копии
Files (worktree $W/tabular-parser, issue в RSHB/arm-tabular-parser): как в задаче 7.
Шаги — задачи 7 с такими отличиями:
| Файл | Правка |
|---|---|
Makefile |
lint: $(BIN)/mypy src arm_statements; check-full: check statements-check test-integration docker-build |
api/pyproject.toml |
known-first-party = ["src", "arm_statements"]; новый блок [[tool.mypy.overrides]] module = ["arm_statements", "arm_statements.*"] с флагами строгого блока arm-core (disallow_any_generics, disallow_subclassing_any, disallow_untyped_calls, disallow_untyped_defs, disallow_incomplete_defs, disallow_untyped_decorators, warn_return_any, warn_unused_ignores, no_implicit_reexport, strict_equality, extra_checks = true) и комментарием про strict = true в overrides mypy 2.x (как у arm-core) |
api/requirements-dev.txt |
ruff==0.16.10, mypy==2.4.0, добавить types-PyYAML==6.0.12.20260906 |
api/pytest.ini, CLAUDE.md |
как у arm-core |
Образ — tabular-parser (docker run --rm tabular-parser python -c …). .dockerignore не менять (исключает
api/tests, но не api/arm_statements). Словари api/rules/statement_*.yaml сервиса этим PR не удаляются — это
план 4.
Task 9: scan-gateway — подключение копии
Files (worktree $W/scan-gateway, issue в RSHB/arm-scan-gateway): как в задаче 7, без requirements-dev.txt
(пины уже ruff==0.16.10, mypy==2.4.0, types-PyYAML==6.0.12.20260906).
| Файл | Правка |
|---|---|
Makefile |
lint: $(BIN)/mypy src arm_statements; в check-full после $(MAKE) check — $(MAKE) statements-check; в help — строка statements-check - свежесть копии api/arm_statements (sync-statements --check), входит в check-full; lint в help — mypy src arm_statements |
api/pyproject.toml |
known-first-party = ["src", "arm_statements"]; в строгий блок module добавить "arm_statements", "arm_statements.*" |
api/pytest.ini, CLAUDE.md |
как у arm-core |
Образ — scan-gateway:dev. per-file-ignores "tests/**" на копию не распространяется — так и должно быть (P1-3).
Что должны включить планы 3, 4, 5
Подготовку хостов делает этот план (задачи 7–9, P2-5); планы хостов стартуют от main с копией. Им остаётся:
| План 3 (arm-core, часть Б) | План 4 (tabular-parser) | План 5 (scan-gateway) | |
|---|---|---|---|
| Старт | после слияния PR задачи 7 (у плана 3 это уже записано: «PR плана 2 в arm-core слит до части Б») | после слияния PR задачи 8 | после слияния PR задачи 9 |
Нужна версия пакета новее, чем в main хоста |
../docs/scripts/sync-statements.sh --in-place --host-dir . в worktree фичи; копия — отдельным коммитом chore(arm_statements): копия пакета arm-statements@<v7>; руками не править |
то же | то же |
make check-full перед PR |
statements-check — 0 или 10; 10 (отстала / перечень кодов) — в описание PR |
то же | то же |
service.statements_version |
— (ядро читает statements_version() для STATEMENTS_VERSION_MISMATCH) |
результат пишет statements_version() пакета |
то же |
| Словари сервиса | api/rules/statement_checks.yaml удалить (копия §7), statement_lines.yaml остаётся словарём имён — statements-check сверяет его коды с перечнем копии |
api/rules/statement_checks.yaml, statement_headers.yaml, statement_periods.yaml удалить (переехали в пакет, P1-13) |
api/rules/ocr_lookalikes.yaml, statement_labels.yaml, statement_lines.yaml — по плану 5 (в пакет переезжают двойники и подписи) |
| Не делать | не добавлять per-file-ignores, exclude или ослабляющие overrides для arm_statements; не менять пины ruff/mypy без пакета; api/conftest.py с autouse не заводить; api/arm_statements не добавлять в .dockerignore; Dockerfile не менять |
то же | то же |
Решения автора плана без обсуждения (P2-n)
Переносятся в design.md задачей 6 (дата 18.10.2026).
- P2-1 (O-1 плана 1). Файл конфига линтеров в копию не кладётся; «конфиг пакета подключён к хосту» — это: ruff хоста
уже совпадает с пакетом (
line-length = 120,py311,E F I UP B PL), в isortknown-first-partyдобавленarm_statements; вmypyхоста модулиarm_statements.*входят в строгий блок (те же флаги, чтоstrict = trueпакета). Цена: блок флагов повторён в трёх хостах; если хост когда-нибудь станет строже пакета, копия покраснеет в хосте — чинить вarm-statements. - P2-2 (O-2). Пины
ruff==0.16.10,mypy==2.4.0,types-PyYAML==6.0.12.20260906во всех хостах (у tabular-parser и arm-core были>=; в их окружениях уже стоят эти версии — пины ничего не переформатируют).pytestне закрепляется: тесты пакета не зависят от его версии. Цена: обновление ruff/mypy — согласованно, сначала пакет. - P2-3 (O-3).
testpaths = tests arm_statements/testsвpytest.iniхостов;conftestхостов не меняется: у них нетapi/conftest.py, аtests/conftest.pyдействует только наtests/;env,asyncio_mode,--strict-markersтестам пакета не мешают. Проверка — равное число собранных тестов пакета в хосте и вarm-statements. - P2-4 (O-4). Dockerfile хостов не меняется:
COPY api/ ./уже кладётapi/arm_statements/rules/*.yaml,.dockerignoreих не исключает; платформа уже возитapi/rules/*.yamlиapi/contracts/(сервисы читают их с диска в контуре). Проверка — запускdefault_rules()в собранном образе (задачи 7–9). - P2-5. Подключение хостов — три bounded PR этого плана (задачи 7–9), а не части планов 3/4/5: правки одинаковые и
механические, план 3 уже ждёт «PR плана 2 в arm-core», первая раскладка проверяет скрипт на настоящих хостах, а планы
4/5 стартуют от
mainс копией. Цена: ещё три PR и копия вmainхостов до того, как её вызывает код сервиса. - P2-6. Режим
--in-place(раскладка в существующий worktree без issue, коммита и PR) — для подключения хоста и для PR фичи, которой нужна новая версия пакета; спека дополнена (задача 6). Цена: второй путь раскладки, мимо отдельного PR синхронизации. - P2-7. Скрипт — bash по образцу
sync-web.sh(spec называетsync-statements.sh), разбор TOML/YAML — помощникstatements_check.py(Python 3.11 + PyYAML); интерпретатор —STATEMENTS_PY→docs/.venv→api/.venvхоста →python3; подходящего нет — код2. Цена:make venvdocs ставит PyYAML (как и в PR #117). - P2-8. В
--checkотсутствие копии —20(«копии нет»): хост, в котором вызывают проверку, обязан держать копию. В обычном режиме хост без копии —2с указанием на--in-place: подключение хоста — ручная задача с правкой конфигов. - P2-9. Нет
contracts/statement-code-orders.yamlв docs —10(docs старше пакета; так до слияния PR #117). - P2-10. Зависимости сверяются по имени (PEP 503) с
api/requirements.txtхоста (его ставит образ); разные версии — примечание, код не меняют. Цена: несовпадение версии PyYAML ловят только тесты пакета в хосте. - P2-11. «Копия отстала» — буквально по спеке:
VERSION≠--ref, даже если новые коммиты источника не трогаютarm_statements/(README, Makefile). Цена: лишние предупреждения10после таких коммитов (см. O-1). - P2-12. Ошибка
fetchв--checkне фатальна (предупреждение, сравнение с локальными ссылками): проверка перед PR не должна краснеть без сети, если копия на месте. - P2-13. Сравнение — по файлам рабочего дерева (
diff -rq), безVERSIONи кэшей (__pycache__,*.pyc,.pytest_cache,.mypy_cache,.ruff_cache,.DS_Store); биты режима не сравниваются. Незакоммиченная правка — тоже20. - P2-14. Обычный режим перед коммитом гоняет
make venvиmake checkхоста в новом worktree (какsync-web.sh— проверки фронта). Цена: минута на окружение. - P2-15. «Сначала ядро» при синхронизации сервиса — предупреждение (в выводе и в описании PR), не отказ: порядок важен для смены формата результата, а не для каждой правки пакета.
- P2-16.
make -C docs statements-check— обзор по трём хостам рядом (0/10— зелёные); вmake checkdocs не входит: читает соседние репозитории. - P2-17. Имя хоста —
--hostили последний компонентorigin(arm-core,arm-tabular-parser,arm-scan-gateway), иначе имя каталога; сверка словаря имён — только уarm-core. - P2-18. Раскладка отказывает (код 1), если файлы копии игнорируются
.gitignoreхоста или содержат то, на чём остановится снимок для контура (/Users/,/home/…,git.aepif.ru,uv venv,uv pip). - P2-19. Строки
make venvdocs — дословно как в веткеfeat/116-unified-normalization, строки таблицыdev/checks.md, которые меняет PR #117, не трогаются: два PR сливаются в любом порядке без конфликта. - P2-20. В
CLAUDE.mdкаждого хоста — правило «копию не править, обновлять скриптом»: агенты читают его первым. Шаблоныdocs/repo-templates/не меняются (разошлись с репозиториями давно). - P2-21. Ветка docs плана — от
origin/main(шаг 1 выкладки раньше PR #117); спека,design.mdи раздел «Копия»dev/checks.mdправятся вfeat/116-unified-normalization(задача 6).
Открытые вопросы (O-n)
- O-1. P2-11: считать ли копию синхронной, если коммиты после
VERSIONне меняютarm_statements/и[project].dependencies? Спека говорит «это коммит--ref»; смягчение уберёт шум, но поменяет требование. - O-2. Есть ли в Nexus банка
ruff==0.16.10,mypy==2.4.0,types-PyYAML==6.0.12.20260906(цельvenvснимка контура ставитrequirements-dev.txtиз Nexus)? У scan-gateway эти пины уже есть — вероятно, да; подтвердить при следующем переносе. - O-3.
prepare-contour-snapshot.shпроверяет отставаниеarm-core/web(sync-web --check), но не свежесть копииarm_statements. Добавить шагsync-statements --checkв подготовку снимка (предупреждение вSNAPSHOT.md) — по решению владельца; в этом плане нет. - O-4.
CLAUDE.mdworkspace (таблица репозиториев) иcontext/00-workspace-layout.mdне знают проarm-statements/; правит план 1 (П-2) или отдельная правка docs — решить контроллеру плана 1.