#!/usr/bin/env bash set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "${repo_root}" fail() { printf 'doc status check failed: %s\n' "$*" >&2 exit 1 } require_file() { local file="$1" [[ -f "${file}" ]] || fail "missing file: ${file}" } require_contains() { local file="$1" local needle="$2" grep -Fq -- "${needle}" "${file}" || fail "${file} missing required text: ${needle}" } require_regex() { local file="$1" local pattern="$2" grep -Eiq -- "${pattern}" "${file}" || fail "${file} missing required pattern: ${pattern}" } required_docs=( "README.md" "CONTRIBUTING.md" "AGENTS.md" "DOCS_INDEX.md" "CURRENT_STATUS.md" "USERGUIDE.md" "PROJECT_PLAN.md" "docs/architecture/README.md" "docs/architecture/adr/0004-rust-bat-go-bat-api-resource-boundary.md" "docs/architecture/assetbundle.md" "docs/architecture/resource-release-layout.md" "docs/guides/development.md" "docs/guides/deployment.md" "docs/reference/rpc-backend-api.md" "docs/reports/CURRENT_GAPS.md" "docs/reports/GO_STATUS.md" "docs/reports/BAT_API_CONTRACT_FIXTURE_HANDOFF.md" "docs/reports/historical/PARSER_FREEZE.md" "api/openapi/bat-api.yaml" "internal/api/openapi.go" "internal/api/testdata/contract/README.md" ) for file in "${required_docs[@]}"; do require_file "${file}" done [[ ! -e "Agents.md" ]] || fail "stale agent document path exists: Agents.md" [[ ! -e "CHECK.md" ]] || fail "obsolete CHECK.md still exists" [[ ! -e "docs/reports/PARSER_FREEZE.md" ]] || fail "active parser freeze document still exists" go_version="$(sed -n 's/^go[[:space:]]\{1,\}//p' go.mod | head -n 1)" [[ -n "${go_version}" ]] || fail "go.mod is missing a Go version" require_contains "README.md" "Go ${go_version}+" require_contains "docs/guides/development.md" "Go ${go_version}+" require_contains "DOCS_INDEX.md" "## 1. 项目入口与协作规则" require_contains "DOCS_INDEX.md" "## 3. 架构、决策与稳定契约" require_contains "DOCS_INDEX.md" "## 7. 历史归档" require_contains "DOCS_INDEX.md" "docs/reports/historical/" require_contains "docs/architecture/README.md" "目标设计" require_contains "docs/architecture/README.md" "实际实现状态以源码、测试和根目录 \`CURRENT_STATUS.md\` 为准;\`PROJECT_PLAN.md\` 只描述目标和路线图" require_contains "docs/architecture/README.md" "确定性 Binary hunk diff/apply" if grep -Fq "bsdiff" docs/architecture/README.md; then fail "docs/architecture/README.md still describes Binary Patch as bsdiff" fi require_contains "docs/architecture/README.md" "Rust \`bat\` 的 mock provider worker" require_contains "docs/architecture/README.md" "**目标 Provider**" require_contains "docs/architecture/README.md" "DeepL Provider" require_contains "README.md" "mock/Crowdin provider worker" require_contains "README.md" "Translation Memory、Glossary 和完整 Provider 扩展体系" require_contains "PROJECT_PLAN.md" "正式 Rust \`bat\` CLI 和命令体系;Go 侧面向 \`bat-api\`、SDK 和服务集成发展" if grep -Fq "Go CLI 主入口和命令体系" PROJECT_PLAN.md; then fail "PROJECT_PLAN.md still presents Go CLI as the primary CLI" fi require_contains "docs/reports/historical/PARSER_FREEZE.md" "**状态**:历史记录,已解除" require_contains "docs/reports/historical/PARSER_FREEZE.md" "**生效时间**:2026-07-30" require_contains "docs/reports/historical/PARSER_FREEZE.md" "**解除时间**:2026-09-04" require_contains "docs/architecture/adr/0004-rust-bat-go-bat-api-resource-boundary.md" 'Rust `bat` 是官方资源生产者和状态拥有者' require_contains "docs/architecture/adr/0001-engine-and-application-boundaries.md" "资源同步职责已由 ADR 0004 取代" placeholder_readmes=( "api/README.md" "api/proto/README.md" "internal/config/README.md" "internal/downloader/README.md" "internal/extractor/README.md" "internal/manifest/README.md" "internal/storage/README.md" "pkg/README.md" "pkg/cas/README.md" "pkg/translator/README.md" "pkg/types/README.md" "web/shared/README.md" ) for file in "${placeholder_readmes[@]}"; do require_file "${file}" require_regex "${file}" "not implemented" require_contains "${file}" "docs/reports/GO_STATUS.md" done require_contains "USERGUIDE.md" "scope=resource_bootstrap_only" require_contains "USERGUIDE.md" "package_update_manifest=false" require_contains "docs/reports/GO_STATUS.md" "resource bootstrap + CDN path" require_contains "docs/reports/GO_STATUS.md" "internal/api/testdata/contract/" require_contains "CURRENT_STATUS.md" "cmd/bat-api" require_contains "CURRENT_STATUS.md" "internal/api/testdata/contract/" require_contains "CURRENT_STATUS.md" "不提供官方账号登录、游戏网关协议或完整 package update manifest" if grep -Fq "响应只来自已发布 snapshot/RPC,不提供登录、网关、鉴权" CURRENT_STATUS.md; then fail "CURRENT_STATUS.md contradicts its documented bat-api token authentication" fi if grep -Fq "ufw allow 5432/tcp" docs/guides/deployment.md || grep -Fq "ufw allow 6379/tcp" docs/guides/deployment.md; then fail "deployment guide recommends opening database ports" fi require_contains "docs/guides/deployment.md" "PostgreSQL 和 Redis 不是当前 \`bat\` / \`bat-api\` 的生产运行依赖" require_contains "docs/guides/deployment.md" "私网、VPN 或 SSH tunnel" require_contains "docs/guides/deployment.md" "尤其不得把 Redis 公网暴露作为推荐方案" require_contains "deployments/docker-compose.remote-db.yml" '"127.0.0.1:5432:5432"' require_contains "deployments/docker-compose.remote-db.yml" '"127.0.0.1:6379:6379"' require_contains "deployments/docker-compose.dev.yml" '"127.0.0.1:5432:5432"' require_contains "deployments/docker-compose.dev.yml" '"127.0.0.1:6379:6379"' if grep -Fq '"0.0.0.0:5432:5432"' deployments/docker-compose.remote-db.yml || grep -Fq '"0.0.0.0:6379:6379"' deployments/docker-compose.remote-db.yml || grep -Fq '"0.0.0.0:5432:5432"' deployments/docker-compose.dev.yml || grep -Fq '"0.0.0.0:6379:6379"' deployments/docker-compose.dev.yml; then fail "database compose files still publish ports on all interfaces" fi require_contains "docs/reports/CURRENT_GAPS.md" "不是完整官方游戏 API" require_contains "docs/reports/CURRENT_GAPS.md" "daemon.clean-stable" require_contains "docs/reference/rpc-backend-api.md" "daemon.restart" require_contains "docs/reference/rpc-backend-api.md" "daemon.clean-stable" require_contains "docs/reference/rpc-backend-api.md" "localized_release_status" require_contains "docs/reference/rpc-backend-api.md" "official_distribution_ready" require_contains "docs/reference/rpc-backend-api.md" "release.attestation" require_contains "docs/reference/rpc-backend-api.md" "expected_manifest_identity" require_contains "docs/reference/rpc-backend-api.md" "expected_verification_generation" require_contains "docs/architecture/resource-release-layout.md" "release.attestation" require_contains "Makefile" "check-docs:" require_contains "Makefile" "check-go-format:" require_contains "Makefile" "format: fmt" require_contains "Makefile" "ci-check:" require_contains "Makefile" "ci: ci-check" require_file "scripts/ci-check.sh" require_file "scripts/check-go-format.sh" require_file "scripts/ci-versions.sh" require_contains "scripts/ci-check.sh" "RUN required:" require_contains "scripts/ci-check.sh" "make check-go-format" require_contains "scripts/ci-check.sh" "Go lint version" require_contains "scripts/ci-check.sh" "GOLANGCI_LINT_VERSION" require_contains "scripts/ci-versions.sh" 'GOLANGCI_LINT_VERSION="2.12.2"' require_contains "docs/guides/development.md" "golangci-lint --version" require_contains "docs/guides/development.md" "required gate" if grep -RIEq --exclude=check-doc-status.sh --exclude-dir=.git --exclude-dir=archive --exclude-dir=historical \ -- '可选 lint|optional lint|optional golangci-lint' .; then fail "current documentation still describes Go lint as optional" fi if grep -Fq "ci: fmt" Makefile; then fail "Makefile ci target must not run the mutating fmt target" fi require_contains ".gitea/workflows/bat.yml" "make check-docs" require_contains ".gitea/workflows/bat.yml" "make test-go-api" require_contains ".gitea/workflows/bat.yml" "go vet ./internal/api/... ./internal/backendrpc/... ./cmd/bat-api/..." require_contains ".gitea/workflows/bat.yml" "go build -o /tmp/bat-api ./cmd/bat-api" require_contains ".gitea/workflows/bat.yml" "Check Go formatting" require_contains ".gitea/workflows/bat.yml" "make check-go-format" require_contains ".gitea/workflows/bat.yml" "Required Go lint" require_contains ".gitea/workflows/bat.yml" "source scripts/ci-versions.sh" require_contains "Makefile" "cargo clippy --workspace --all-targets -- -D warnings" openapi_tmp="$(mktemp)" trap 'rm -f "${openapi_tmp}"' EXIT awk ' /^const openAPISpecYAML = `/ { started = 1 sub(/^const openAPISpecYAML = `/, "") print next } started && /^`$/ { exit } started { print } ' internal/api/openapi.go > "${openapi_tmp}" cmp -s "api/openapi/bat-api.yaml" "${openapi_tmp}" || fail "static OpenAPI document differs from internal/api/openapi.go" bash scripts/check-doc-links.sh printf 'doc status check ok\n'