#!/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, при падении снимка нет, у сервисов без фронта не вызывается"