arm-ka-docs/scripts/prepare-contour-snapshot.sh
Andrey Epifancev 6edb62c592 docs(dev): снимок для контура вырезает openspec/config.yaml
Вопрос разработчика при переносе в контур: .claude/ и CLAUDE.md
вырезаются, а openspec/config.yaml с контекстом агента остаётся.
Решение владельца 06.10.2026: вырезать, как CLAUDE.md. Блоки
context/rules — инструкции агенту с путями ../docs и ../fixtures,
которые в контуре никуда не ведут; openspec CLI в контуре нет,
specs/ и design.md читаются без него.

- prepare-contour-snapshot.sh: rm, проверка раздела 5, SNAPSHOT.md
- contour-transfer-guide.md: таблица раздела 1, разделы 2, 3, 9

Проверено прогоном скрипта на tabular-parser@3b32462.

Closes #36

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 16:52:00 +04:00

161 lines
9.1 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Снимок сервисного репозитория АРМ КА для переноса в контур банка (dev/contour-transfer-guide.md).
#
# Что делает: берёт дерево коммита (`git archive`, без истории), вырезает то, чего в контуре нет или что ему не нужно
# (AI-инструментарий, планы, Dockerfile для локальной сборки), переводит `make venv` с uv на `python -m venv` + pip через
# Nexus (как `setup/setup.sh` шаблона), убирает цель docker-build, заменяет ссылки на внешний Gitea, пишет SNAPSHOT.md с
# провенансом и проверяет результат. Репозиторий-источник не меняется.
#
# Запуск из workspace: docs/scripts/prepare-contour-snapshot.sh <каталог репозитория> <каталог вывода> [ref]
# ref по умолчанию — origin/main (снимок всегда с сервера, не с рабочего дерева).
# Пример: docs/scripts/prepare-contour-snapshot.sh tabular-parser /tmp/contour
# Результат: <каталог вывода>/<имя репозитория>/ — готовое дерево для rsync в клон репозитория контура.
set -euo pipefail
usage() { echo "usage: $0 <repo-dir> <out-dir> [ref]" >&2; exit 2; }
[ $# -ge 2 ] || usage
REPO=$(cd "$1" && pwd)
mkdir -p "$2"
OUT=$(cd "$2" && pwd)
REF=${3:-origin/main}
NAME=$(basename "$REPO")
DEST="$OUT/$NAME"
case "$NAME" in
arm-core|tabular-parser|scan-gateway) ;;
*) echo "ошибка: $NAME — не сервисный репозиторий (ожидаются arm-core, tabular-parser, scan-gateway); docs и fixtures в контур не переносятся" >&2; exit 2 ;;
esac
if [ -e "$DEST" ]; then
echo "ошибка: $DEST уже существует — удалите или укажите другой каталог вывода" >&2
exit 2
fi
git -C "$REPO" fetch -q origin
HASH=$(git -C "$REPO" rev-parse "$REF")
DATE=$(git -C "$REPO" show -s --format=%cs "$REF")
SUBJECT=$(git -C "$REPO" show -s --format=%s "$REF")
mkdir -p "$DEST"
git -C "$REPO" archive "$REF" | tar -x -C "$DEST"
# --- 1. Вырезать то, чего в контуре нет ------------------------------------------------------------------------
# .claude/, CLAUDE.md, openspec/config.yaml — инструкции и контекст AI-агента (config.yaml: блоки context/rules с путями
# ../docs и ../fixtures; openspec CLI в контуре нет, specs/ и design.md читаются без него); docs/superpowers/ — планы и
# ledger (в них локальные пути); Dockerfile/.dockerignore — локальная сборка образа, платформа собирает образ своим CI.
# openspec/specs/ и openspec/changes/archive/ остаются: это спецификация проекта и решения (design.md).
rm -rf "$DEST/.claude" "$DEST/CLAUDE.md" "$DEST/openspec/config.yaml" "$DEST/docs/superpowers" "$DEST/Dockerfile" "$DEST/.dockerignore"
rmdir "$DEST/docs" 2>/dev/null || true
# --- 2. Makefile: venv через python -m venv + pip с Nexus, без uv; без docker-build --------------------------------
python3 -I - "$DEST/Makefile" <<'PY'
import re
import sys
from pathlib import Path
path = Path(sys.argv[1])
lines = path.read_text(encoding="utf-8").splitlines()
out: list[str] = []
i = 0
venv_patched = False
while i < len(lines):
line = lines[i]
# рецепт venv: все строки с uv заменяются одним рецептом на pip
if line.startswith("venv:"):
comment_start = len(out)
while comment_start > 0 and out[comment_start - 1].startswith("#"):
comment_start -= 1
del out[comment_start:]
out.append("# Контур банка: Python 3.11 платформы, pip через Nexus (setup/pip.conf), как в setup/setup.sh; dev-зависимости")
out.append("# тянут requirements.txt. PYTHON переопределяется: make venv PYTHON=/usr/bin/python3.11")
out.append("PYTHON ?= python3.11")
out.append("venv:")
out.append("\tcd api && $(PYTHON) -m venv .venv")
out.append("\tcp setup/pip.conf api/.venv/pip.conf")
out.append("\tcd api && .venv/bin/pip install -r requirements-dev.txt")
i += 1
while i < len(lines) and lines[i].startswith("\t"):
i += 1
venv_patched = True
continue
# цель docker-build и комментарий над ней удаляются
if line.startswith("docker-build:"):
comment_start = len(out)
while comment_start > 0 and out[comment_start - 1].startswith("#"):
comment_start -= 1
del out[comment_start:]
i += 1
while i < len(lines) and lines[i].startswith("\t"):
i += 1
if i < len(lines) and lines[i] == "" and out and out[-1] == "":
i += 1
continue
if line.startswith("check-full:"):
line = re.sub(r"\s+docker-build\b", "", line)
if line.startswith("\t$(MAKE) docker-build"):
i += 1
continue
if line.startswith(".PHONY:"):
line = re.sub(r"\s+docker-build\b", "", line)
if line.startswith('\t@echo "docker-build'):
i += 1
continue
line = line.replace(" + docker-build", "")
out.append(line)
i += 1
if not venv_patched:
sys.exit("Makefile: цель venv не найдена — скрипт устарел относительно репозитория")
path.write_text("\n".join(out) + "\n", encoding="utf-8")
PY
# --- 3. Ссылки на внешний Gitea → имя репозитория и номер ---------------------------------------------------------
# В openspec-архивах и README встречаются ссылки вида https://git.aepif.ru/RSHB/<repo>/issues/5 и pulls/7.
while IFS= read -r file; do
[ -n "$file" ] || continue
python3 -I - "$file" <<'PY'
import re
import sys
from pathlib import Path
path = Path(sys.argv[1])
text = path.read_text(encoding="utf-8")
text = re.sub(r"https?://git\.aepif\.ru/(RSHB/[\w.-]+)/(issues|pulls)/(\d+)", r"\1#\3", text)
text = re.sub(r"https?://git\.aepif\.ru/?", "внешний Gitea проекта", text)
path.write_text(text, encoding="utf-8")
PY
done < <(grep -rIl "git.aepif.ru" "$DEST" 2>/dev/null || true)
# --- 4. Провенанс ---------------------------------------------------------------------------------------------------
cat > "$DEST/SNAPSHOT.md" <<EOF
# Снимок $NAME для контура
Источник: внешний репозиторий проекта \`RSHB/$NAME\`, коммит \`$HASH\` ($DATE, «${SUBJECT}»).
Снимок без истории; подготовлен \`prepare-contour-snapshot.sh\` ($(date +%Y-%m-%d)).
Чего здесь нет по сравнению с источником: \`.claude/\`, \`CLAUDE.md\`, \`openspec/config.yaml\`, \`docs/superpowers/\`
(инструментарий, контекст агента и планы разработки снаружи), \`Dockerfile\`/\`.dockerignore\` (образ собирает платформа). Соседних репозиториев \`docs\` и
\`fixtures\` в контуре нет: тесты идентичности контракта и тесты с маркером \`fixtures\` пропускаются — это ожидаемо.
Копия контракта — \`api/contracts/\`, спецификация — \`openspec/specs/\`, решения — \`openspec/changes/archive/*/design.md\`.
Разработка ведётся снаружи; правки в контуре возвращаются в источник вручную (см. dev/contour-transfer-guide.md в docs).
EOF
# --- 5. Проверки ----------------------------------------------------------------------------------------------------
fail=0
for p in .claude CLAUDE.md openspec/config.yaml docs/superpowers Dockerfile .dockerignore; do
if [ -e "$DEST/$p" ]; then echo "ошибка: в снимке остался $p" >&2; fail=1; fi
done
if grep -rIn "/Users/\|/home/[a-z]" "$DEST" >/dev/null 2>&1; then
echo "ошибка: в снимке есть локальные пути:" >&2; grep -rIn "/Users/\|/home/[a-z]" "$DEST" >&2; fail=1
fi
if grep -rIn "git.aepif.ru\|uv venv\|uv pip" "$DEST" >/dev/null 2>&1; then
echo "ошибка: остались ссылки на внешний Gitea или uv:" >&2; grep -rIn "git.aepif.ru\|uv venv\|uv pip" "$DEST" >&2; fail=1
fi
for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test api/contracts; do
if [ ! -e "$DEST/$p" ]; then echo "ошибка: платформенный файл $p отсутствует" >&2; fail=1; fi
done
[ "$fail" = 0 ] || exit 1
echo "снимок готов: $DEST"
echo "источник: $NAME@${HASH:0:7} ($DATE); файлов: $(find "$DEST" -type f | wc -l | tr -d ' ')"