From fdd25a6000fa1e960f696c0805dc92a5ac0cf70d Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 18 Oct 2026 19:31:22 +0400 Subject: [PATCH] =?UTF-8?q?fix(scripts):=20sync-statements.sh=20--in-place?= =?UTF-8?q?=20=D0=BD=D0=B5=20=D0=BF=D0=B8=D1=88=D0=B5=D1=82=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE=D0=B9=20checkout=20?= =?UTF-8?q?=D0=B8=20main,=20--check=20=D0=B2=D1=81=D0=B5=D0=B3=D0=B4=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=87=D0=B0=D1=82=D0=B0=D0=B5=D1=82=20=D0=B8?= =?UTF-8?q?=D1=82=D0=BE=D0=B3,=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BA=D0=BE=D0=B2=20(#120)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- Makefile | 2 +- dev/checks.md | 2 +- dev/contour-transfer-guide.md | 4 +-- scripts/prepare-contour-snapshot.sh | 1 + scripts/sync-statements.sh | 48 ++++++++++++++++++++++------- scripts/test_sync_statements.sh | 19 ++++++++++++ 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index e1b539a..c494bc7 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ test-sync-statements: sync-web: scripts/sync-web.sh $(SYNC_ARGS) -# Копия пакета arm_statements в хост одной командой (worktree, issue, PR); SYNC_ARGS="--host arm-core [--check|--dry-run|--in-place]" +# Копия пакета arm_statements в хост одной командой (worktree, issue, PR); SYNC_ARGS="--host arm-core [--check|--dry-run]" sync-statements: STATEMENTS_PY=$(PY) scripts/sync-statements.sh $(SYNC_ARGS) diff --git a/dev/checks.md b/dev/checks.md index 8fb4c63..afd55eb 100644 --- a/dev/checks.md +++ b/dev/checks.md @@ -135,7 +135,7 @@ PR убедитесь, что `docs` и `fixtures` доступны (пропу | `make validate-openspec` | `openspec validate slice-0-skeleton --strict` (другое изменение: `make validate-openspec CHANGE=`) | | `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` | копия пакета `arm_statements` в хост одной командой (worktree, issue, PR); `SYNC_ARGS="--host arm-core [--check\|--dry-run\|--in-place]"` | +| `make sync-statements` | копия пакета `arm_statements` в хост одной командой (worktree, issue, PR); `SYNC_ARGS="--host arm-core [--check\|--dry-run]"` | | `make statements-check` | обзор свежести копий по трём хостам рядом (`arm-core`, `tabular-parser`, `scan-gateway`): строка итога на хост; в `check` не входит | Без `make`: `python scripts/validate_contracts.py` (нужен `jsonschema`) и `openspec validate --strict`. diff --git a/dev/contour-transfer-guide.md b/dev/contour-transfer-guide.md index c71de15..e1d9a64 100644 --- a/dev/contour-transfer-guide.md +++ b/dev/contour-transfer-guide.md @@ -92,8 +92,8 @@ docs/scripts/prepare-contour-snapshot.sh scan-gateway /tmp/contour `.env.test`, `contracts/`). Любая проблема — код возврата 1 и список. Тест скрипта — `make test-snapshot` (синтетический репозиторий с артефактами агента в корне и в `web/`); проверка -отставания `web/` и остановка переноса — в `make test-sync-web`; проверка копии `arm_statements` (пропуск, отставание, правка) -— там же, в `make test-snapshot` (нужен Python ≥ 3.11 с PyYAML, `make venv`). +отставания `web/` и остановка переноса — в `make test-sync-web`, а проверка копии `arm_statements` (пропуск, отставание, правка) +— в `make test-snapshot` (нужен Python ≥ 3.11 с PyYAML, `make venv`). ### 3.1. Синхронизация `arm-core/web` с `arm-web`: `scripts/sync-web.sh` diff --git a/scripts/prepare-contour-snapshot.sh b/scripts/prepare-contour-snapshot.sh index 2ef089d..5dc47e3 100755 --- a/scripts/prepare-contour-snapshot.sh +++ b/scripts/prepare-contour-snapshot.sh @@ -99,6 +99,7 @@ STATEMENTS_LAG="" if git -C "$REPO" rev-parse -q --verify "$REF:api/arm_statements" >/dev/null 2>&1; then SYNC_STATEMENTS="$(cd "$(dirname "$0")" && pwd)/sync-statements.sh" STM_TMP=$(mktemp -d) + trap 'rm -rf "$STM_TMP"' EXIT git -C "$REPO" archive "$REF" api | tar -x -C "$STM_TMP" rc=0 # shellcheck disable=SC2086 diff --git a/scripts/sync-statements.sh b/scripts/sync-statements.sh index bdfde2a..16344da 100755 --- a/scripts/sync-statements.sh +++ b/scripts/sync-statements.sh @@ -23,7 +23,11 @@ # --source каталог arm-statements, по умолчанию /arm-statements; --docs — каталог docs # STATEMENTS_PY — интерпретатор помощника (Python >= 3.11 с PyYAML); иначе docs/.venv, хост api/.venv, python3 # --check важнее --dry-run и --in-place при любом порядке аргументов. -# Итог --check: «statements-check <хост>: <код> — …»; код возврата равен коду в строке; 2 — неверные аргументы или окружение. +# Итог --check: «statements-check <хост>: <код> — …»; код возврата равен коду в строке. +# Коды выхода: 0 — готово/синхронно (--in-place: итог проверки 0 или 10, он же код); 1 — сбой раскладки, защит, проверок хоста, +# коммита или push; 2 — неверные аргументы, окружение, нет копии на --host-ref, worktree уже есть; 10 — предупреждение +# (только --check и --in-place); 20 — расхождение или отказ (правка копии, чужая версия, нет зависимости, код вне перечня). +# --in-place требует --host-dir (клон/worktree хоста на ветке не main); основной checkout и ветку main он отвергает. set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" @@ -61,14 +65,16 @@ while [ $# -gt 0 ]; do --docs) [ $# -ge 2 ] || die "--docs требует значение" 2; DOCS=$2; shift ;; --core-dir) [ $# -ge 2 ] || die "--core-dir требует значение" 2; CORE_DIR=$2; shift ;; --wt-root) [ $# -ge 2 ] || die "--wt-root требует значение" 2; WT_ROOT=$2; shift ;; - -h|--help) sed -n "2,26p" "$0"; exit 0 ;; + -h|--help) sed -n "2,30p" "$0"; exit 0 ;; *) die "неизвестный аргумент $1" 2 ;; esac shift done if [ "$CHECK" = 1 ]; then MODE=check elif [ "$IN_PLACE" = 1 ] && [ "$DRY" = 1 ]; then die "--in-place несовместим с --dry-run" 2 -elif [ "$IN_PLACE" = 1 ]; then MODE=in-place +elif [ "$IN_PLACE" = 1 ]; then + MODE=in-place + [ -n "$HOST_DIR" ] || die "--in-place требует --host-dir ; --host один не годится: он указывает на основной checkout" 2 elif [ "$DRY" = 1 ]; then MODE=dry else MODE=sync; fi @@ -200,14 +206,22 @@ evaluate() { fi # 5. зависимости пакета в requirements хоста git -C "$SRC" show "$version:pyproject.toml" > "$TMPD/pyproject.toml" 2>/dev/null || : > "$TMPD/pyproject.toml" - helper deps "$TMPD/pyproject.toml" "$reqs" - if [ "$HELPER_RC" = 20 ]; then bad "$HELPER_OUT"; elif [ -n "$HELPER_OUT" ]; then echo "$HELPER_OUT"; fi + if [ ! -f "$reqs" ]; then + bad "нет requirements хоста (api/requirements.txt): зависимости пакета не проверены" + else + helper deps "$TMPD/pyproject.toml" "$reqs" + if [ "$HELPER_RC" = 20 ]; then bad "$HELPER_OUT"; elif [ -n "$HELPER_OUT" ]; then echo "$HELPER_OUT"; fi + fi fi # 6. коды словаря имён строк (только у хоста со словарём) if [ -n "$lines" ]; then - helper lines "$lines" "$copy/rules/statement_code_orders.yaml" - if [ "$HELPER_RC" = 20 ]; then bad "$HELPER_OUT"; fi + if [ ! -f "$copy/rules/statement_code_orders.yaml" ]; then + bad "в копии нет rules/statement_code_orders.yaml: коды словаря имён не проверены" + else + helper lines "$lines" "$copy/rules/statement_code_orders.yaml" + if [ "$HELPER_RC" = 20 ]; then bad "$HELPER_OUT"; fi + fi fi # 7. перечень кодов в docs if [ ! -f "$DOCS/contracts/statement-code-orders.yaml" ]; then @@ -280,6 +294,11 @@ lay_copy() { } if [ "$MODE" = in-place ]; then + HB=$(git -C "$HOST_DIR" symbolic-ref -q --short HEAD 2>/dev/null || true) + if [ "$HOST_DIR" = "$WS/$HOST_NAME" ]; then + die "--in-place не пишет в основной checkout хоста ($HOST_DIR): создайте worktree задачи и укажите его в --host-dir" 2 + fi + case "$HB" in main|master) die "--in-place не пишет в ветку $HB хоста: перейдите на ветку задачи (worktree)" 2 ;; esac COPY="$HOST_DIR/api/arm_statements" REQS="$HOST_DIR/api/requirements.txt" LINES="" @@ -291,6 +310,7 @@ if [ "$MODE" = in-place ]; then evaluate "$COPY" "$REQS" "" "$HOST_NAME" > "$TMPD/ev.out" if [ "$COPY_BAD" = 1 ]; then cat "$TMPD/ev.out" + echo "если копия «чужая» после squash-слияния ветки arm-statements: удалите api/arm_statements и разложите заново" echo "statements-check $HOST_NAME: 20 — отказ: копия правлена или чужая, ничего не записано" exit 20 fi @@ -298,8 +318,11 @@ if [ "$MODE" = in-place ]; then # 2. зависимости и словарь имён по коммиту --ref refuse="" git -C "$SRC" show "$REF_SHA:pyproject.toml" > "$TMPD/ref-pyproject.toml" 2>/dev/null || : > "$TMPD/ref-pyproject.toml" - helper deps "$TMPD/ref-pyproject.toml" "$REQS" - if [ "$HELPER_RC" = 20 ]; then refuse="$HELPER_OUT"; fi + if [ ! -f "$REQS" ]; then refuse="нет requirements хоста (api/requirements.txt)" + else + helper deps "$TMPD/ref-pyproject.toml" "$REQS" + if [ "$HELPER_RC" = 20 ]; then refuse="$HELPER_OUT"; fi + fi if [ -n "$LINES" ]; then git -C "$SRC" show "$REF_SHA:arm_statements/rules/statement_code_orders.yaml" > "$TMPD/ref-orders.yaml" 2>/dev/null \ || : > "$TMPD/ref-orders.yaml" @@ -367,8 +390,11 @@ OLD7=${OLD:0:7} # 3. зависимости и словарь имён против TARGET refuse="" git -C "$SRC" show "$TARGET:pyproject.toml" > "$TMPD/ref-pyproject.toml" 2>/dev/null || : > "$TMPD/ref-pyproject.toml" -helper deps "$TMPD/ref-pyproject.toml" "$HT/api/requirements.txt" -if [ "$HELPER_RC" = 20 ]; then refuse=$HELPER_OUT; fi +if [ ! -f "$HT/api/requirements.txt" ]; then refuse="нет requirements хоста (api/requirements.txt)" +else + helper deps "$TMPD/ref-pyproject.toml" "$HT/api/requirements.txt" + if [ "$HELPER_RC" = 20 ]; then refuse=$HELPER_OUT; fi +fi if [ -n "$HLINES" ]; then git -C "$SRC" show "$TARGET:arm_statements/rules/statement_code_orders.yaml" > "$TMPD/ref-orders.yaml" 2>/dev/null \ || : > "$TMPD/ref-orders.yaml" diff --git a/scripts/test_sync_statements.sh b/scripts/test_sync_statements.sh index 6be64ab..c513446 100755 --- a/scripts/test_sync_statements.sh +++ b/scripts/test_sync_statements.sh @@ -207,6 +207,15 @@ sync 20 "check: код словаря вне перечня" --check --host-dir check "$(has 1120 && has fsbu4 && echo 0 || echo 1)" "check: код вне перечня — 1120 и fsbu4" reset_hosts +rm "$AC/arm_statements/rules/statement_code_orders.yaml" +sync 20 "check: нет statement_code_orders.yaml в копии" --check --host-dir "$WORK/arm-core" +check "$(has 'statements-check arm-core: 20' && has 'rules/statement_code_orders.yaml' && echo 0 || echo 1)" "check: нет перечня в копии — находка и итоговая строка" +reset_hosts +rm "$AC/requirements.txt" +sync 20 "check: нет requirements.txt" --check --host-dir "$WORK/arm-core" +check "$(has 'statements-check arm-core: 20' && has 'requirements' && echo 0 || echo 1)" "check: нет requirements — находка и итоговая строка" +reset_hosts + echo "# новый код" >> "$WORK/docs/contracts/statement-code-orders.yaml" sync 10 "check: перечень в docs новее" --check --host-dir "$WORK/arm-core" check "$(has contracts/statement-code-orders.yaml && has 'statements-check arm-core: 10' && echo 0 || echo 1)" "check: перечень в docs — в выводе файл и итог 10" @@ -247,6 +256,16 @@ git -C "$FR" checkout -q -b main put "$FR/api/requirements.txt" $'PyYAML==6.0.3' commit_push "$FR" "fresh" main +# хосты — на ветку задачи: --in-place отвергает main/master +for h in arm-core tabular-parser scan-gateway; do git -C "$WORK/$h" checkout -q -b work; done +git -C "$FR" checkout -q -b work +sync 2 "in-place: --host без --host-dir" --in-place --host tabular-parser +check "$(has -- '--host-dir' && echo 0 || echo 1)" "in-place: --host без --host-dir — сообщение" +git -C "$WORK/scan-gateway" checkout -q main +sync 2 "in-place: ветка main хоста" --in-place --host-dir "$WORK/scan-gateway" +check "$(has main && [ "$(tr -d '\n' < "$WORK/scan-gateway/api/arm_statements/VERSION")" = "$S1" ] && echo 0 || echo 1)" "in-place: main — копия не тронута" +git -C "$WORK/scan-gateway" checkout -q work + sync 0 "in-place: первичная раскладка" --in-place --host-dir "$FR" --ref origin/main check "$([ "$(tr -d '\n' < "$FR/api/arm_statements/VERSION")" = "$S2" ] && echo 0 || echo 1)" "in-place: VERSION = полный хэш S2" rm -rf "$WORK/arc"; mkdir -p "$WORK/arc"; git -C "$SRC" archive "$S2" arm_statements | tar -x -C "$WORK/arc"