arm-ka-docs/scripts/test_prepare_contour_snapshot.sh
2026-10-18 19:24:42 +04:00

233 lines
14 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
# Тест prepare-contour-snapshot.sh на синтетическом репозитории arm-core: артефакты агента вырезаются на любой глубине
# (в т.ч. во вложенном web/), спецификации и код остаются. Запуск: make test-snapshot (или напрямую из корня docs).
set -euo pipefail
SCRIPT="$(cd "$(dirname "$0")" && pwd)/prepare-contour-snapshot.sh"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
git init -q --bare "$WORK/origin.git"
git clone -q "$WORK/origin.git" "$WORK/src/arm-core" 2>/dev/null
R="$WORK/src/arm-core"
put() { mkdir -p "$(dirname "$R/$1")"; printf '%s\n' "${2:-x}" > "$R/$1"; }
# платформенные файлы, без которых проверка снимка падает
printf 'venv:\n\tuv venv api/.venv\n\n# Фронт: biome, типы, сборка\nweb-check:\n\tcd web && npm install\n\tcd web && npx biome check src\n\tcd web && npx tsc --noEmit\n\tcd web && npm run build\n\ncheck-full: check web-check\n' > "$R/Makefile"
for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test \
api/contracts/schema.json; do put "$p"; done
# корень: артефакты агента и локальная сборка
for p in .claude/skills/a/SKILL.md CLAUDE.md openspec/config.yaml docs/superpowers/plans/p.md Dockerfile .dockerignore; do
put "$p" "/Users/someone/projects"
done
put openspec/specs/osv/spec.md
# вложенный фронт (как web/ из arm-web): те же артефакты на второй глубине
for p in web/.claude/commands/opsx/apply.md web/.codegraph/.gitignore web/CLAUDE.md web/openspec/config.yaml \
web/docs/superpowers/plans/2026-10-08-demo.md; do
put "$p" "/Users/someone/projects"
done
put web/openspec/specs/osv-ui/spec.md
put web/openspec/changes/archive/2026-10-08-demo/design.md
put web/src/App.tsx
cat > "$R/web/package.json" <<'JSON'
{
"name": "frontend",
"private": true,
"scripts": {
"build": "rsbuild build",
"test": "vitest run",
"format": "eslint --fix src && prettier --write src",
"lint": "eslint src && prettier --check src",
"start": "rsbuild dev --open",
"preview": "rsbuild preview"
},
"dependencies": {"antd": "^6.5.0", "react": "^18.3.1"},
"devDependencies": {
"@biomejs/biome": "1.9.4", "@rsbuild/core": "1.6.7", "@rsbuild/plugin-react": "^1.1.0", "@types/react": "^18.3.1",
"daisyui": "^4.12.10", "eslint": "9.17.0", "prettier": "3.3.3", "tailwindcss": "^3.4.13", "typescript": "^5.7.3",
"vitest": "3.1.4"
},
"optionalDependencies": {"@biomejs/cli-linux-x64-musl": "^1.9.4", "raisa-ui": "^1.0.0"}
}
JSON
put web/docs/notes.md # прочее содержимое вложенного docs/ остаётся
git -C "$R" add -A
git -C "$R" -c user.name=t -c user.email=t@t commit -qm init
git -C "$R" push -q origin HEAD:main 2>/dev/null
git -C "$R" fetch -q origin
# проверка сборки web/ в тесте отключена (нет сети); её включённый режим проверяется ниже заглушкой npm
SNAPSHOT_SKIP_WEB_BUILD=1 "$SCRIPT" "$R" "$WORK/out" origin/main >/dev/null
D="$WORK/out/arm-core"
fail=0
for p in .claude CLAUDE.md openspec/config.yaml docs Dockerfile .dockerignore \
web/.claude web/.codegraph web/CLAUDE.md web/openspec/config.yaml web/docs/superpowers; do
if [ -e "$D/$p" ]; then echo "FAIL: в снимке остался $p" >&2; fail=1; fi
done
for p in openspec/specs/osv/spec.md web/openspec/specs/osv-ui/spec.md web/openspec/changes/archive/2026-10-08-demo/design.md \
web/src/App.tsx web/docs/notes.md SNAPSHOT.md; do
if [ ! -e "$D/$p" ]; then echo "FAIL: из снимка пропал $p" >&2; fail=1; fi
done
[ "$fail" = 0 ] || exit 1
echo "ok: prepare-contour-snapshot.sh вырезает артефакты агента на любой глубине"
# --- web/package.json урезан до зависимостей сборки -------------------------------------------------------------------
python3 -I - "$D/web/package.json" <<'PY' || fail=1
import json, sys
pk = json.load(open(sys.argv[1], encoding="utf-8"))
errs = []
if sorted(pk["devDependencies"]) != ["@rsbuild/core", "@rsbuild/plugin-react", "daisyui", "tailwindcss"]:
errs.append(f"devDependencies: {sorted(pk['devDependencies'])}")
if pk["devDependencies"]["@rsbuild/core"] != "1.6.7":
errs.append("версии devDependencies должны сохраниться")
if pk["dependencies"] != {"antd": "^6.5.0", "react": "^18.3.1"}:
errs.append("dependencies изменились")
if pk["optionalDependencies"] != {"raisa-ui": "^1.0.0"}:
errs.append(f"optionalDependencies: {pk['optionalDependencies']}")
if sorted(pk["scripts"]) != ["build", "preview", "start"]:
errs.append(f"scripts: {sorted(pk['scripts'])}")
if errs:
print("FAIL:", *errs, sep="\n ", file=sys.stderr)
sys.exit(1)
PY
# Makefile: web-check без biome/tsc, check-full на месте
if grep -Eq "biome|tsc" "$D/Makefile"; then echo "FAIL: в Makefile снимка остались biome/tsc" >&2; fail=1; fi
grep -q "cd web && npm install" "$D/Makefile" && grep -q "cd web && npm run build" "$D/Makefile" \
|| { echo "FAIL: web-check потерял npm install/build" >&2; fail=1; }
grep -q "^check-full:.*web-check" "$D/Makefile" || { echo "FAIL: check-full не ссылается на web-check" >&2; fail=1; }
# SNAPSHOT.md перечисляет убранное
for w in "@biomejs/biome" "eslint" "prettier" "typescript" "vitest" "@types/react" "@biomejs/cli-linux-x64-musl" \
"test" "lint" "format"; do
grep -q "$w" "$D/SNAPSHOT.md" || { echo "FAIL: SNAPSHOT.md не упоминает убранное: $w" >&2; fail=1; }
done
[ "$fail" = 0 ] || exit 1
echo "ok: web/package.json, Makefile и SNAPSHOT.md урезаны под сборку"
# --- проверка сборки web/ (включена по умолчанию): заглушка npm в PATH -----------------------------------------------
STUB="$WORK/stub"; mkdir -p "$STUB"
cat > "$STUB/npm" <<'SH'
#!/usr/bin/env bash
# заглушка: install создаёт node_modules, build — dist; NPM_STUB_FAIL=build роняет сборку
echo "$*" >> "$NPM_STUB_LOG"
case "$1" in
install) mkdir -p node_modules; echo "$PWD" >> "$NPM_STUB_LOG.cwd" ;;
run) if [ "${NPM_STUB_FAIL:-}" = build ]; then echo "Module not found: eslint" >&2; exit 1; fi; mkdir -p dist ;;
esac
SH
chmod +x "$STUB/npm"
export NPM_STUB_LOG="$WORK/npm.log"
# сборка проходит: снимок выдан, node_modules/dist в него не попали, исходный каталог вывода чист
PATH="$STUB:$PATH" "$SCRIPT" "$R" "$WORK/out-ok" origin/main >/dev/null
O="$WORK/out-ok/arm-core"
grep -q "^install" "$NPM_STUB_LOG" && grep -q "^run build" "$NPM_STUB_LOG" \
|| { echo "FAIL: проверка сборки не вызвала npm install и npm run build" >&2; fail=1; }
for p in web/node_modules web/dist web/package-lock.json; do
[ ! -e "$O/$p" ] || { echo "FAIL: в снимке остался $p" >&2; fail=1; }
done
[ -e "$O/web/package.json" ] || { echo "FAIL: после проверки сборки пропал web/package.json" >&2; fail=1; }
# сборка падает: код 1, понятное сообщение, снимка нет
rc=0
PATH="$STUB:$PATH" NPM_STUB_FAIL=build "$SCRIPT" "$R" "$WORK/out-bad" origin/main >"$WORK/bad.out" 2>&1 || rc=$?
[ "$rc" = 1 ] || { echo "FAIL: при падении сборки ожидался код 1, получен $rc" >&2; fail=1; }
grep -q "не в разрешённом списке" "$WORK/bad.out" || { echo "FAIL: нет сообщения про разрешённый список" >&2; fail=1; }
[ ! -e "$WORK/out-bad/arm-core" ] || { echo "FAIL: при падении сборки снимок выдан" >&2; fail=1; }
# tabular-parser и scan-gateway фронта не имеют: npm не вызывается
: > "$NPM_STUB_LOG"
git init -q --bare "$WORK/tp-origin.git"
git clone -q "$WORK/tp-origin.git" "$WORK/src/tabular-parser" 2>/dev/null
T="$WORK/src/tabular-parser"
printf 'venv:\n\tuv venv api/.venv\n' > "$T/Makefile"
for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/requirements.txt api/.env.test api/contracts/s.json; do
mkdir -p "$(dirname "$T/$p")"; echo x > "$T/$p"
done
git -C "$T" add -A; git -C "$T" -c user.name=t -c user.email=t@t commit -qm init
git -C "$T" push -q origin HEAD:main 2>/dev/null; git -C "$T" fetch -q origin
PATH="$STUB:$PATH" "$SCRIPT" "$T" "$WORK/out-tp" origin/main >/dev/null
[ ! -s "$NPM_STUB_LOG" ] || { echo "FAIL: для tabular-parser вызван npm" >&2; fail=1; }
[ "$fail" = 0 ] || exit 1
echo "ok: проверка сборки web/ — выдаёт снимок без node_modules/dist, при падении снимка нет, у сервисов без фронта не вызывается"
# --- копия arm_statements в сервисе: sync-statements.sh --check (Р-42) -------------------------------------------------
# Синтетический arm-statements и хосты; флаги sync-statements.sh — через SYNC_STATEMENTS_ARGS (реальные репозитории не нужны).
PY=${STATEMENTS_PY:-python3}
if ! "$PY" -c 'import tomllib, yaml' 2>/dev/null; then
echo "нужен Python >= 3.11 с PyYAML: выполните make venv (или задайте STATEMENTS_PY)" >&2
exit 1
fi
export STATEMENTS_PY="$PY"
export GIT_AUTHOR_NAME=t GIT_AUTHOR_EMAIL=t@t GIT_COMMITTER_NAME=t GIT_COMMITTER_EMAIL=t@t
SS="$WORK/arm-statements"
git init -q --bare "$WORK/arm-statements.git"
git clone -q "$WORK/arm-statements.git" "$SS" 2>/dev/null
git -C "$SS" checkout -q -b main
printf '[project]\nname = "arm-statements"\ndependencies = ["PyYAML==6.0.3"]\n' > "$SS/pyproject.toml"
mkdir -p "$SS/arm_statements"
echo "# init" > "$SS/arm_statements/__init__.py"
echo "пакет" > "$SS/README.md"
mkdir -p "$SS/arm_statements/rules"
printf 'version: 1\nrepeats: {}\neditions:\n 66n:\n BALANCE: {full: ["1110", "1100"], simplified: ["1100"]}\n' \
> "$SS/arm_statements/rules/statement_code_orders.yaml"
git -C "$SS" add -A; git -C "$SS" commit -qm S1; git -C "$SS" push -q origin HEAD:main 2>/dev/null; git -C "$SS" fetch -q origin
S1=$(git -C "$SS" rev-parse HEAD)
mkdir -p "$WORK/docs-x/contracts"
cp "$SS/arm_statements/rules/statement_code_orders.yaml" "$WORK/docs-x/contracts/statement-code-orders.yaml"
export SYNC_STATEMENTS_ARGS="--source $SS --docs $WORK/docs-x"
# mk_service <имя>: сервис с копией пакета на S1 в api/arm_statements (после коммита — origin/main)
mk_service() {
local name=$1 d="$WORK/src/$1"
git init -q --bare "$WORK/$name-o.git"
git clone -q "$WORK/$name-o.git" "$d" 2>/dev/null
printf 'venv:\n\tuv venv api/.venv\n' > "$d/Makefile"
for p in raisa_config.yml setup/setup.sh setup/pip.conf api/run_server.py api/.env.test api/contracts/s.json; do
mkdir -p "$(dirname "$d/$p")"; echo x > "$d/$p"
done
printf 'aiohttp==3.14.3\nPyYAML==6.0.3\n' > "$d/api/requirements.txt"
git -C "$SS" archive "$S1" arm_statements | tar -x -C "$d/api"
echo "$S1" > "$d/api/arm_statements/VERSION"
git -C "$d" add -A; git -C "$d" commit -qm init; git -C "$d" push -q origin HEAD:main 2>/dev/null; git -C "$d" fetch -q origin
}
# хост без api/arm_statements (tabular-parser выше): проверка пропущена молча
: > "$WORK/tp.err"
"$SCRIPT" "$T" "$WORK/out-tp2" origin/main >/dev/null 2>"$WORK/tp.err"
[ ! -s "$WORK/tp.err" ] && ! grep -q arm_statements "$WORK/out-tp2/tabular-parser/SNAPSHOT.md" \
|| { echo "FAIL: хост без arm_statements: проверка не пропущена молча" >&2; fail=1; }
# копия на актуальной версии: тихо, абзаца нет
mk_service scan-gateway
G="$WORK/src/scan-gateway"
"$SCRIPT" "$G" "$WORK/out-g1" origin/main >/dev/null 2>"$WORK/g1.err"
[ ! -s "$WORK/g1.err" ] && ! grep -q arm_statements "$WORK/out-g1/scan-gateway/SNAPSHOT.md" \
|| { echo "FAIL: актуальная копия: ожидалась тишина и SNAPSHOT.md без абзаца" >&2; cat "$WORK/g1.err" >&2; fail=1; }
# нет источника (код 2): предупреждение, снимок делается
rc=0
SYNC_STATEMENTS_ARGS="--source $WORK/nope --docs $WORK/docs-x" "$SCRIPT" "$G" "$WORK/out-g2" origin/main >/dev/null 2>"$WORK/g2.err" || rc=$?
[ "$rc" = 0 ] && [ -e "$WORK/out-g2/scan-gateway/SNAPSHOT.md" ] && grep -q "arm_statements" "$WORK/g2.err" \
|| { echo "FAIL: нет источника: ожидалось предупреждение и снимок (rc=$rc)" >&2; fail=1; }
# копия отстала (код 10): предупреждение в stderr и абзац в SNAPSHOT.md, снимок делается
echo "пакет, правка" > "$SS/README.md"
git -C "$SS" add -A; git -C "$SS" commit -qm S2; git -C "$SS" push -q origin HEAD:main 2>/dev/null; git -C "$SS" fetch -q origin
rc=0
"$SCRIPT" "$G" "$WORK/out-g3" origin/main >/dev/null 2>"$WORK/g3.err" || rc=$?
[ "$rc" = 0 ] && grep -q "arm_statements" "$WORK/g3.err" && grep -q "Копия пакета arm_statements" "$WORK/out-g3/scan-gateway/SNAPSHOT.md" \
|| { echo "FAIL: копия отстала: ожидались предупреждение и абзац в SNAPSHOT.md (rc=$rc)" >&2; cat "$WORK/g3.err" >&2; fail=1; }
# прямая правка копии (код 20): код 1, снимка нет
echo "# правка" >> "$G/api/arm_statements/__init__.py"
git -C "$G" add -A; git -C "$G" commit -qm "direct edit"; git -C "$G" push -q origin HEAD:main 2>/dev/null; git -C "$G" fetch -q origin
rc=0
"$SCRIPT" "$G" "$WORK/out-g4" origin/main >/dev/null 2>"$WORK/g4.err" || rc=$?
[ "$rc" = 1 ] && [ ! -e "$WORK/out-g4/scan-gateway" ] && grep -q "arm_statements" "$WORK/g4.err" \
|| { echo "FAIL: прямая правка копии: ожидался код 1 без снимка (rc=$rc)" >&2; cat "$WORK/g4.err" >&2; fail=1; }
[ "$fail" = 0 ] || exit 1
echo "ok: копия arm_statements — пропуск без пакета, тишина при актуальной, предупреждение при отставании, стоп при правке"