# Check an Astro project against astro-architect house rules.
# 1. If the target has an astro.config.* AND an `astro` CLI is reachable
# (repo-local node_modules/.bin/astro, else `npx astro --version` succeeds),
# run `astro check` there — a real compiler/type check. Errors reported
# by `astro check` are a hard failure (exit 1).
# 2. Otherwise degrade to static, grep-based heuristic checks that need no
# external tool: content-collection schemas should use `z.object(`, and
# `client:*` hydration directives should not be uniformly `client:load`.
# Heuristic misses are warnings only (exit 0) — never a crash, never a
# Usage: check_astro.sh [TARGET_PROJECT_PATH]
# TARGET_PROJECT_PATH defaults to "."
if [[ ! -d "$TARGET" ]]; then
echo "Usage: $0 [TARGET_PROJECT_PATH]" >&2
echo "error: '$TARGET' is not a directory" >&2
warn() { printf '⚠️ %s\n' "$1"; }
error() { printf '❌ %s\n' "$1"; fail=1; }
ok() { printf '✅ %s\n' "$1"; }
astro_config=$(find "$TARGET" -maxdepth 1 -name 'astro.config.*' 2>/dev/null | head -n1)
if [[ -x "$TARGET/node_modules/.bin/astro" ]]; then
echo "$TARGET/node_modules/.bin/astro"
if command -v npx >/dev/null 2>&1; then
if (cd "$TARGET" && npx --no-install astro --version >/dev/null 2>&1); then
echo "npx --no-install astro"
if [[ -n "$astro_config" ]] && ASTRO_BIN=$(resolve_astro_cli); then
if (cd "$TARGET" && eval "$ASTRO_BIN check"); then
ok "astro check reported no errors"
error "astro check reported errors — see output above"
warn "no astro.config.* found or astro CLI not reachable — skipping 'astro check', falling back to static heuristics"
echo "== heuristic: content collection schemas use z.object( =="
content_config=$(find "$TARGET/src/content" -maxdepth 1 -name 'config.*' 2>/dev/null | head -n1)
if [[ -n "$content_config" ]]; then
if grep -q 'z\.object(' "$content_config" 2>/dev/null; then
ok "z.object( schema found in $content_config"
warn "content config exists ($content_config) but no z.object( schema found — collections may be unvalidated"
ok "no src/content config file found — skipping"
echo "== heuristic: client:* directive eagerness =="
astro_files=$(find "$TARGET" -type f -name '*.astro' -not -path '*/node_modules/*' 2>/dev/null)
if [[ -n "$astro_files" ]]; then
directive_hits=$(grep -ohE 'client:[a-zA-Z-]+' $astro_files 2>/dev/null || true)
hit_count=$(echo "$directive_hits" | grep -c . || true)
if [[ "$hit_count" -ge 2 ]]; then
non_load=$(echo "$directive_hits" | grep -vc '^client:load$' || true)
if [[ "$non_load" -eq 0 ]]; then
warn "found $hit_count client:* directives, all client:load — hydration may be uniformly eager (consider client:idle/visible/media/only where they work)"
ok "found $hit_count client:* directives with mixed eagerness (not all client:load)"
ok "fewer than 2 client:* directives found — nothing to compare"
ok "no .astro files found — skipping"
if [[ "$fail" -eq 0 ]]; then
echo "All checks passed."
echo "One or more checks failed — see ❌ lines above."