Files
boshu2__agentops/scripts/pre-push-gate.sh
T
Boden Fuller cc6750c4e8 feat(docs,skills): rebuild Pages site with MkDocs Material + harden RPI orchestrator delegation contract
## Docs site — boshu2.github.io/agentops

- Replace Jekyll minima with MkDocs Material (slate dark palette default)
- Landing hero scoped to .landing-hero (no gradient bleed on inner pages)
- Section landings (getting-started, architecture, reference, contracts, levels) with narrative ledes
- Generate skill catalog + per-skill pages from skills/*/SKILL.md at build
- Generate CLI reference from cli/docs/ at build (fails build on missing source)
- Fix every internal link via one-shot migration (scripts/migrations/2026-04-19-fix-docs-links.py)
- Wire mkdocs build --strict into pre-push gate (Check 25a)
- Replace .github/workflows/pages.yml with .github/workflows/docs.yml (actions/deploy-pages)
- README + docs/CONTRIBUTING updated with docs-build toolchain on-ramp
- tests/docs/validate-links.sh patched to understand mkdocs-generated paths

## RPI orchestrator strict-delegation contract

- Strict sub-skill delegation is now the default for /rpi, /discovery, /validation. No --full flag — always on.
- Canonical contract in skills/shared/references/strict-delegation-contract.md (plus Codex mirror)
- Forged learning with detection phrases + corrective actions (ao-inject-ready)
- Rename --no-lifecycle -> --no-scaffold in /discovery (Claude + Codex) with deprecation alias through v2.40.0
- Harden inline-work hotspots with orchestrator-owned markers in /crank STEPS 3a.3, 6.5, 8.7 + Inline Work Policy footer
- Flag consistency: --auto and --interactive scopes documented; --complexity syntax formalized
- CHANGELOG [Unreleased] documents + scaffolds; runtime hook enforcement explicitly deferred

## fix(hooks): go-test-precommit skips when no Go files staged

- Previously ran go vet + go build unconditionally, blocking non-Go commits when Go toolchain was missing or at a different version
- Now checks staged diff and exits cleanly if no *.go or go.{mod,sum,work} files are staged
- Applied to both hooks/ (source) and cli/embedded/hooks/ (synced)

Provenance: full RPI lifecycle with strict delegation at every phase boundary (Phase 1 Skill(discovery) -> Phase 2 Skill(crank) -> Phase 3 Skill(validation) -> Skill(vibe), Skill(post-mortem), Skill(retro)).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:37:58 -04:00

973 lines
31 KiB
Bash
Executable File

#!/usr/bin/env bash
# pre-push-gate.sh — lightweight validation before push
#
# Runs the minimum checks to prevent broken code from landing on main.
# Designed to be fast (~10-20s cached) while catching the failures that
# ci-local-release.sh would catch later.
#
# Checks:
# 1. Go build + vet (if cli/ changed)
# 2. Go race tests on changed packages (via validate-go-fast.sh)
# 3. Command/test pairing for cli/cmd/ao Go changes
# 4. cmd/ao coverage floor gate
# 4b. Per-package coverage ratchet (full mode only)
# 5. Embedded hooks sync (cli/embedded/ matches hooks/)
# 6. Skill count sync
# 7. Worktree disposition
# 8. Skill runtime/CLI parity
# 9. Codex skill parity (skipped — manually maintained)
# 10. Codex install bundle parity (skipped — manually maintained)
# 11. Codex runtime section format
# 12. Skill integrity (references/xrefs)
# 13. Skill lint suite
# 14. Skill schema validation
# 15. Manifest schema validation
# 16. Codex artifact metadata
# 17. Codex backbone prompts
# 18. Codex override coverage
# 19. Next-work contract parity
# 19b. Retrieval quality ratchet (warn-only until 500 indexed turns)
# 20. Skill runtime formats
# 21. Codex RPI contract validation
# 22. Codex lifecycle guard validation
# 23. Skill CLI snippets
# 24. Headless runtime skill smoke (full mode only)
# 24b. CLI docs parity
# --- shifted from CI-only (v2.32) ---
# 25. Doc-release stabilization gate
# 25b. Release audit artifact refs
# 26. Contract compatibility
# 27. Hook preflight
# 28. Hooks/docs parity
# 29. CI policy parity
# 30. ShellCheck (fast: changed .sh only)
# 31. Plugin load test (symlink rejection)
# 32. Learning coherence
# 33. BATS orphan hooks audit
# 34. Skill citation parity (ao lookup → ao metrics cite)
# 35. Flywheel health (warn only, non-blocking)
#
# Usage:
# scripts/pre-push-gate.sh [--scope auto|upstream|staged|worktree|head]
# scripts/pre-push-gate.sh --fast [--scope ...] # only checks relevant to changed files
# (also called from .githooks/pre-push)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$REPO_ROOT"
if [[ -n "${GIT_DIR:-}" && -z "${GIT_WORK_TREE:-}" ]]; then
export GIT_WORK_TREE="$REPO_ROOT"
fi
run_without_git_env() {
local var_name
local -a env_args=(env)
while IFS='=' read -r var_name _; do
[[ "$var_name" == GIT_* ]] || continue
env_args+=("-u" "$var_name")
done < <(env)
"${env_args[@]}" "$@"
}
run_without_git_env_and_stdin() {
run_without_git_env "$@" </dev/null
}
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
errors=0
skipped=0
SCOPE="${PRE_PUSH_GO_SCOPE:-upstream}"
FAST_MODE=false
pass() { echo -e "${GREEN} ok${NC} $1"; }
fail() { echo -e "${RED}FAIL${NC} $1"; errors=$((errors + 1)); }
skip() { echo -e " -- $1 (skipped)"; skipped=$((skipped + 1)); }
warn() { echo -e "${YELLOW}WARN${NC} $1"; }
indent_output() {
while IFS= read -r line; do
printf ' %s\n' "$line"
done <<<"$1"
}
usage() {
cat <<'EOF'
Usage: scripts/pre-push-gate.sh [--fast] [--scope auto|upstream|staged|worktree|head]
Options:
--fast Only run checks relevant to changed files (~15-30s vs ~3min)
--scope How to determine changed files (default: upstream)
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--fast)
FAST_MODE=true
shift
;;
--scope)
SCOPE="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown arg: $1" >&2
usage >&2
exit 2
;;
esac
done
case "$SCOPE" in
auto|upstream|staged|worktree|head) ;;
*)
echo "Invalid --scope: $SCOPE" >&2
usage >&2
exit 2
;;
esac
collect_all_changed() {
case "$SCOPE" in
upstream)
git diff --name-only '@{upstream}...HEAD' 2>/dev/null || true
;;
staged)
git diff --name-only --cached 2>/dev/null || true
;;
worktree)
{
git diff --name-only --cached 2>/dev/null || true
git diff --name-only 2>/dev/null || true
} | sed '/^[[:space:]]*$/d' | sort -u
;;
head)
git show --name-only --pretty=format: HEAD 2>/dev/null || true
;;
auto)
{
git diff --name-only '@{upstream}...HEAD' 2>/dev/null || true
git diff --name-only --cached 2>/dev/null || true
git diff --name-only 2>/dev/null || true
} | sed '/^[[:space:]]*$/d' | sort -u
;;
esac
}
# --- Fast mode: detect changed file categories ---
HAS_GO=1
HAS_SKILL=1
HAS_HOOK=1
HAS_DOCS=1
HAS_SHELL=1
HAS_LEARNING=1
if [[ "$FAST_MODE" == "true" ]]; then
all_changed="$(collect_all_changed)"
if echo "$all_changed" | grep -qE '^cli/'; then
HAS_GO=1
else
HAS_GO=0
fi
if echo "$all_changed" | grep -qE '^skills/|^skills-codex|^tests/skills/'; then
HAS_SKILL=1
else
HAS_SKILL=0
fi
if echo "$all_changed" | grep -qE '^hooks/|^lib/'; then
HAS_HOOK=1
else
HAS_HOOK=0
fi
if echo "$all_changed" | grep -qE '^docs/|^README\.md|^CHANGELOG|^PRODUCT\.md|^SKILL-TIERS\.md'; then
HAS_DOCS=1
else
HAS_DOCS=0
fi
if echo "$all_changed" | grep -qE '\.sh$'; then
HAS_SHELL=1
else
HAS_SHELL=0
fi
if echo "$all_changed" | grep -qE '^\.agents/learnings/'; then
HAS_LEARNING=1
else
HAS_LEARNING=0
fi
fi
needs_check() {
local category="$1"
if [[ "$FAST_MODE" != "true" ]]; then
return 0
fi
case "$category" in
go) [[ "$HAS_GO" -eq 1 ]] ;;
skill) [[ "$HAS_SKILL" -eq 1 ]] ;;
hook) [[ "$HAS_HOOK" -eq 1 ]] ;;
docs) [[ "$HAS_DOCS" -eq 1 ]] ;;
shell) [[ "$HAS_SHELL" -eq 1 ]] ;;
learning) [[ "$HAS_LEARNING" -eq 1 ]] ;;
always) return 0 ;;
*) return 0 ;;
esac
}
collect_go_changed() {
case "$SCOPE" in
upstream)
git diff --name-only '@{upstream}...HEAD' -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
;;
staged)
git diff --name-only --cached -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
;;
worktree)
{
git diff --name-only --cached -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
git diff --name-only -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
} | sed '/^[[:space:]]*$/d' | sort -u
;;
head)
git show --name-only --pretty=format: HEAD -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
;;
auto)
{
git diff --name-only '@{upstream}...HEAD' -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
git diff --name-only --cached -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
git diff --name-only -- 'cli/*.go' 'cli/**/*.go' 'cli/go.mod' 'cli/go.sum' 2>/dev/null || true
} | sed '/^[[:space:]]*$/d' | sort -u
;;
esac
}
if [[ "$FAST_MODE" == "true" ]]; then
echo "pre-push gate (fast): validating changed files before push..."
echo " go=$HAS_GO skill=$HAS_SKILL hook=$HAS_HOOK docs=$HAS_DOCS shell=$HAS_SHELL learning=$HAS_LEARNING"
else
echo "pre-push gate: validating before push..."
fi
# --- 1. Go build + vet ---
if needs_check go; then
if command -v go >/dev/null 2>&1 && [[ -f cli/go.mod ]]; then
go_changed="$(collect_go_changed)"
if [[ -n "$go_changed" ]]; then
if (cd cli && go build -o /dev/null ./cmd/ao 2>&1); then
pass "go build"
else
fail "go build"
fi
if (cd cli && go vet ./... 2>&1); then
pass "go vet"
else
fail "go vet"
fi
else
pass "go build (no Go changes)"
fi
fi
else
skip "go build + vet"
fi
# --- 2. Go race tests on changed scope ---
if needs_check go; then
if [[ -x scripts/validate-go-fast.sh ]]; then
if go_fast_output="$(scripts/validate-go-fast.sh --scope "$SCOPE" 2>&1)"; then
pass "go test -race (changed scope)"
else
fail "go test -race (changed scope)"
indent_output "$go_fast_output"
fi
else
fail "missing executable: scripts/validate-go-fast.sh"
fi
else
skip "go test -race"
fi
# --- 3. Command/test pairing for command-surface changes ---
if needs_check go; then
if [[ -x scripts/check-go-command-test-pair.sh ]]; then
if pair_output="$(scripts/check-go-command-test-pair.sh 2>&1)"; then
pass "command/test pairing"
else
fail "command/test pairing"
indent_output "$pair_output"
fi
else
fail "missing executable: scripts/check-go-command-test-pair.sh"
fi
else
skip "command/test pairing"
fi
# --- 3b. HOME isolation in harvest.*/RunIngest tests ---
if needs_check go; then
if [[ -x scripts/check-home-isolation.sh ]]; then
if home_iso_output="$(scripts/check-home-isolation.sh 2>&1)"; then
pass "HOME isolation in test files"
else
fail "HOME isolation in test files"
indent_output "$home_iso_output"
fi
else
fail "missing executable: scripts/check-home-isolation.sh"
fi
else
skip "HOME isolation in test files"
fi
# --- 3c. Capture ~/.agents hash snapshot (diff'd at end of gate) ---
HASH_GATE_SNAPSHOT=""
if [[ -x scripts/check-agents-hash-snapshot.sh ]]; then
HASH_GATE_SNAPSHOT="$(scripts/check-agents-hash-snapshot.sh capture 2>/dev/null || echo "")"
fi
# --- 5. Embedded hooks sync (full parity gate) ---
if [[ -x scripts/validate-embedded-sync.sh ]]; then
if embed_output="$(./scripts/validate-embedded-sync.sh 2>&1)"; then
pass "embedded hooks in sync"
else
fail "embedded hooks stale (run: cd cli && make sync-hooks)"
indent_output "$embed_output"
fi
else
fail "missing executable: scripts/validate-embedded-sync.sh"
fi
# --- 6. Skill count sync ---
if needs_check skill; then
if [[ -x scripts/sync-skill-counts.sh ]]; then
if scripts/sync-skill-counts.sh --check >/dev/null 2>&1; then
pass "skill counts in sync"
else
fail "skill counts out of sync (run: scripts/sync-skill-counts.sh)"
fi
fi
else
skip "skill counts"
fi
# --- 7. Worktree disposition ---
if [[ -x scripts/check-worktree-disposition.sh ]]; then
if disposition_output="$(scripts/check-worktree-disposition.sh 2>&1)"; then
pass "worktree disposition"
else
fail "worktree disposition"
indent_output "$disposition_output"
fi
else
fail "missing executable: scripts/check-worktree-disposition.sh"
fi
# --- 8. Skill runtime/CLI parity ---
if needs_check skill; then
if [[ -x scripts/validate-skill-runtime-parity.sh ]]; then
if skill_runtime_output="$(scripts/validate-skill-runtime-parity.sh 2>&1)"; then
pass "skill runtime parity"
else
fail "skill runtime parity"
indent_output "$skill_runtime_output"
fi
else
fail "missing executable: scripts/validate-skill-runtime-parity.sh"
fi
else
skip "skill runtime parity"
fi
# --- 9. Codex skill parity --- (removed: skills-codex/ is manually maintained)
skip "codex skill parity (manually maintained)"
# --- 10. Codex install bundle parity --- (removed: skills-codex/ is manually maintained)
skip "codex install bundle parity (manually maintained)"
# --- 11. Codex runtime section format ---
if needs_check skill; then
if [[ -x scripts/validate-codex-runtime-sections.sh ]]; then
if codex_runtime_output="$(scripts/validate-codex-runtime-sections.sh 2>&1)"; then
pass "codex runtime sections"
else
fail "codex runtime sections"
indent_output "$codex_runtime_output"
fi
else
fail "missing executable: scripts/validate-codex-runtime-sections.sh"
fi
else
skip "codex runtime sections"
fi
# --- 12. Skill integrity ---
if needs_check skill; then
if [[ -x skills/heal-skill/scripts/heal.sh ]]; then
if skill_integrity_output="$(bash skills/heal-skill/scripts/heal.sh --strict 2>&1)"; then
pass "skill integrity"
else
fail "skill integrity"
indent_output "$skill_integrity_output"
fi
else
fail "missing executable: skills/heal-skill/scripts/heal.sh"
fi
else
skip "skill integrity"
fi
# --- 13. Skill lint suite ---
if needs_check skill; then
if [[ -x tests/skills/run-all.sh ]]; then
if skill_lint_output="$(bash tests/skills/run-all.sh 2>&1)"; then
pass "skill lint suite"
else
fail "skill lint suite"
indent_output "$skill_lint_output"
fi
else
fail "missing executable: tests/skills/run-all.sh"
fi
else
skip "skill lint suite"
fi
# --- 14. Skill schema validation ---
if needs_check skill; then
if [[ -x scripts/validate-skill-schema.sh ]]; then
if skill_schema_output="$(scripts/validate-skill-schema.sh 2>&1)"; then
pass "skill schema validation"
else
fail "skill schema validation"
indent_output "$skill_schema_output"
fi
else
fail "missing executable: scripts/validate-skill-schema.sh"
fi
else
skip "skill schema validation"
fi
# --- 15. Manifest schema validation ---
if needs_check skill; then
if [[ -x scripts/validate-manifests.sh ]]; then
if manifest_output="$(scripts/validate-manifests.sh --repo-root . 2>&1)"; then
pass "manifest schema validation"
else
fail "manifest schema validation"
indent_output "$manifest_output"
fi
else
fail "missing executable: scripts/validate-manifests.sh"
fi
else
skip "manifest schema validation"
fi
# --- 16. Codex artifact metadata ---
if needs_check skill; then
if [[ -x scripts/validate-codex-generated-artifacts.sh ]]; then
if codex_generated_output="$(scripts/validate-codex-generated-artifacts.sh --scope "$SCOPE" 2>&1)"; then
pass "codex artifact metadata"
else
fail "codex artifact metadata"
indent_output "$codex_generated_output"
fi
else
fail "missing executable: scripts/validate-codex-generated-artifacts.sh"
fi
else
skip "codex artifact metadata"
fi
# --- 17. Codex backbone prompts ---
if needs_check skill; then
if [[ -x scripts/validate-codex-backbone-prompts.sh ]]; then
if codex_backbone_output="$(scripts/validate-codex-backbone-prompts.sh 2>&1)"; then
pass "codex backbone prompts"
else
fail "codex backbone prompts"
indent_output "$codex_backbone_output"
fi
else
fail "missing executable: scripts/validate-codex-backbone-prompts.sh"
fi
else
skip "codex backbone prompts"
fi
# --- 18. Codex override coverage ---
if needs_check skill; then
if [[ -x scripts/validate-codex-override-coverage.sh ]]; then
if codex_override_output="$(scripts/validate-codex-override-coverage.sh 2>&1)"; then
pass "codex override coverage"
else
fail "codex override coverage"
indent_output "$codex_override_output"
fi
else
fail "missing executable: scripts/validate-codex-override-coverage.sh"
fi
else
skip "codex override coverage"
fi
# --- 19. Next-work contract parity ---
if [[ -x scripts/validate-next-work-contract-parity.sh ]]; then
if next_work_contract_output="$(scripts/validate-next-work-contract-parity.sh 2>&1)"; then
pass "next-work contract parity"
else
fail "next-work contract parity"
indent_output "$next_work_contract_output"
fi
else
fail "missing executable: scripts/validate-next-work-contract-parity.sh"
fi
# --- 19b. Retrieval quality ratchet ---
if needs_check always; then
if [[ -x scripts/check-retrieval-quality-ratchet.sh ]]; then
if retrieval_quality_output="$(run_without_git_env scripts/check-retrieval-quality-ratchet.sh 2>&1)"; then
if grep -q '^WARN retrieval quality ratchet:' <<<"$retrieval_quality_output"; then
warn "retrieval quality ratchet"
indent_output "$retrieval_quality_output"
else
pass "retrieval quality ratchet"
fi
else
fail "retrieval quality ratchet"
indent_output "$retrieval_quality_output"
fi
else
fail "missing executable: scripts/check-retrieval-quality-ratchet.sh"
fi
fi
# --- 20. Skill runtime formats ---
if needs_check skill; then
if [[ -x scripts/validate-skill-runtime-formats.sh ]]; then
if codex_lint_output="$(scripts/validate-skill-runtime-formats.sh 2>&1)"; then
pass "skill runtime formats"
else
fail "skill runtime formats"
indent_output "$codex_lint_output"
fi
else
fail "missing executable: scripts/validate-skill-runtime-formats.sh"
fi
else
skip "skill runtime formats"
fi
# --- 21. Codex RPI contract validation ---
if needs_check skill; then
if [[ -f scripts/validate-codex-rpi-contract.sh ]]; then
if codex_rpi_contract_output="$(bash scripts/validate-codex-rpi-contract.sh 2>&1)"; then
pass "codex RPI contract"
else
fail "codex RPI contract"
indent_output "$codex_rpi_contract_output"
fi
else
fail "missing file: scripts/validate-codex-rpi-contract.sh"
fi
else
skip "codex RPI contract"
fi
# --- 22. Codex lifecycle guard validation ---
if needs_check skill; then
if [[ -x scripts/validate-codex-lifecycle-guards.sh ]]; then
if codex_lifecycle_output="$(bash scripts/validate-codex-lifecycle-guards.sh 2>&1)"; then
pass "codex lifecycle guards"
else
fail "codex lifecycle guards"
indent_output "$codex_lifecycle_output"
fi
else
fail "missing executable: scripts/validate-codex-lifecycle-guards.sh"
fi
else
skip "codex lifecycle guards"
fi
# --- 23. Skill CLI snippets ---
if needs_check skill; then
if [[ -x scripts/validate-skill-cli-snippets.sh ]]; then
if skill_cli_output="$(run_without_git_env scripts/validate-skill-cli-snippets.sh 2>&1)"; then
pass "skill CLI snippets"
else
fail "skill CLI snippets"
indent_output "$skill_cli_output"
fi
else
fail "missing executable: scripts/validate-skill-cli-snippets.sh"
fi
else
skip "skill CLI snippets"
fi
# --- 24. Headless runtime skill smoke ---
# Skip in fast mode — requires nested Claude/Codex which fails inside Claude sessions
if needs_check always && [[ "$FAST_MODE" != "true" ]]; then
if [[ -x scripts/validate-headless-runtime-skills.sh ]]; then
if runtime_smoke_output="$(scripts/validate-headless-runtime-skills.sh 2>&1)"; then
pass "headless runtime skills"
indent_output "$runtime_smoke_output"
else
fail "headless runtime skills"
indent_output "$runtime_smoke_output"
fi
else
fail "missing executable: scripts/validate-headless-runtime-skills.sh"
fi
else
skip "headless runtime skills"
fi
# --- 24b. CLI docs parity (generate-cli-reference.sh --check) ---
# Trigger on any cmd/ao/*.go change (help text, flags, etc.), not just go build changes
HAS_CMD_AO=0
if [[ "$FAST_MODE" == "true" ]]; then
if echo "$all_changed" | grep -qE '^cli/cmd/ao/.*\.go$'; then
HAS_CMD_AO=1
fi
fi
if needs_check go || [[ "$HAS_CMD_AO" -eq 1 ]]; then
if [[ -x scripts/generate-cli-reference.sh ]]; then
if cli_docs_output="$(run_without_git_env scripts/generate-cli-reference.sh --check 2>&1)"; then
pass "CLI docs parity"
else
fail "CLI docs parity (run: scripts/generate-cli-reference.sh)"
indent_output "$cli_docs_output"
fi
else
fail "missing executable: scripts/generate-cli-reference.sh"
fi
else
skip "CLI docs parity"
fi
# --- 25. Doc-release stabilization gate ---
if needs_check docs || needs_check skill; then
if [[ -x tests/docs/validate-doc-release.sh ]]; then
if doc_release_output="$(./tests/docs/validate-doc-release.sh 2>&1)"; then
pass "doc-release gate"
else
fail "doc-release gate (run: ./tests/docs/validate-doc-release.sh)"
indent_output "$doc_release_output"
fi
else
fail "missing executable: tests/docs/validate-doc-release.sh"
fi
else
skip "doc-release gate"
fi
# --- 25a. MkDocs strict build (fast, optional — requires Python + requirements-docs.txt) ---
if needs_check docs || needs_check skill; then
if [[ "${PRE_PUSH_SKIP_MKDOCS:-0}" == "1" ]]; then
skip "mkdocs strict build (PRE_PUSH_SKIP_MKDOCS=1)"
elif [[ -x scripts/docs-build.sh ]] && command -v python3 >/dev/null 2>&1; then
if mkdocs_output="$(scripts/docs-build.sh --check 2>&1)"; then
pass "mkdocs strict build"
else
fail "mkdocs strict build (run: scripts/docs-build.sh --check)"
indent_output "$mkdocs_output"
fi
else
skip "mkdocs strict build (python3 or scripts/docs-build.sh missing)"
fi
else
skip "mkdocs strict build"
fi
# --- 25b. Release audit artifact refs ---
if needs_check docs || [[ "${all_changed:-}" == *"scripts/validate-release-audit-artifacts.sh"* ]]; then
if [[ -x scripts/validate-release-audit-artifacts.sh ]]; then
if release_audit_artifacts_output="$(scripts/validate-release-audit-artifacts.sh 2>&1)"; then
pass "release audit artifacts"
else
fail "release audit artifacts"
indent_output "$release_audit_artifacts_output"
fi
else
fail "missing executable: scripts/validate-release-audit-artifacts.sh"
fi
else
skip "release audit artifacts"
fi
# --- 26. Contract compatibility ---
if needs_check always; then
if [[ -x scripts/check-contract-compatibility.sh ]]; then
if contract_output="$(./scripts/check-contract-compatibility.sh 2>&1)"; then
pass "contract compatibility"
else
fail "contract compatibility (run: ./scripts/check-contract-compatibility.sh)"
indent_output "$contract_output"
fi
else
fail "missing executable: scripts/check-contract-compatibility.sh"
fi
fi
# --- 26b. Swarm evidence schema validation ---
if needs_check always; then
if [[ -x scripts/validate-swarm-evidence.sh ]]; then
if swarm_evidence_output="$(./scripts/validate-swarm-evidence.sh 2>&1)"; then
pass "swarm evidence schema"
else
fail "swarm evidence schema (run: ./scripts/validate-swarm-evidence.sh)"
indent_output "$swarm_evidence_output"
fi
else
fail "missing executable: scripts/validate-swarm-evidence.sh"
fi
fi
# --- 27. Hook preflight ---
if needs_check hook; then
if [[ -x scripts/validate-hook-preflight.sh ]]; then
if hook_preflight_output="$(./scripts/validate-hook-preflight.sh 2>&1)"; then
pass "hook preflight"
else
fail "hook preflight"
indent_output "$hook_preflight_output"
fi
else
fail "missing executable: scripts/validate-hook-preflight.sh"
fi
else
skip "hook preflight"
fi
# --- 28. Hooks/docs parity ---
if needs_check hook; then
if [[ -x scripts/validate-hooks-doc-parity.sh ]]; then
if hooks_doc_output="$(./scripts/validate-hooks-doc-parity.sh 2>&1)"; then
pass "hooks/docs parity"
else
fail "hooks/docs parity"
indent_output "$hooks_doc_output"
fi
else
fail "missing executable: scripts/validate-hooks-doc-parity.sh"
fi
else
skip "hooks/docs parity"
fi
# --- 29. CI policy parity ---
if needs_check always; then
if [[ -x scripts/validate-ci-policy-parity.sh ]]; then
if ci_policy_output="$(./scripts/validate-ci-policy-parity.sh 2>&1)"; then
pass "CI policy parity"
else
fail "CI policy parity"
indent_output "$ci_policy_output"
fi
else
fail "missing executable: scripts/validate-ci-policy-parity.sh"
fi
fi
# --- 30. ShellCheck on changed scripts ---
if needs_check shell; then
if command -v shellcheck >/dev/null 2>&1; then
shell_errors=0
if [[ "$FAST_MODE" == "true" ]]; then
# Only check changed .sh files
changed_sh="$(echo "$all_changed" | grep '\.sh$' || true)"
if [[ -n "$changed_sh" ]]; then
while IFS= read -r f; do
[[ -f "$f" ]] || continue
if ! shellcheck_out="$(shellcheck -S warning "$f" 2>&1)"; then
shell_errors=1
indent_output "$shellcheck_out"
fi
done <<< "$changed_sh"
fi
else
# Full mode: check all scripts with shebangs
while IFS= read -r f; do
[[ -f "$f" ]] || continue
head -1 "$f" | grep -q '^#!' || continue
if ! shellcheck_out="$(shellcheck -S warning "$f" 2>&1)"; then
shell_errors=1
indent_output "$shellcheck_out"
fi
done < <(find scripts hooks lib bin -name '*.sh' -type f 2>/dev/null)
fi
if [[ "$shell_errors" -eq 0 ]]; then
pass "shellcheck"
else
fail "shellcheck"
fi
else
skip "shellcheck (not installed)"
fi
else
skip "shellcheck"
fi
# --- 31. Plugin load test (symlinks + manifest) ---
if needs_check always; then
symlink_found=0
while IFS= read -r _; do
symlink_found=1
break
done < <(find skills hooks lib scripts -type l 2>/dev/null)
if [[ "$symlink_found" -eq 0 ]]; then
pass "no symlinks"
else
fail "symlinks found (CI rejects all symlinks)"
fi
fi
# --- 32. Learning coherence ---
if needs_check learning; then
if [[ -x tests/validate-learning-coherence.sh ]]; then
if learning_output="$(bash tests/validate-learning-coherence.sh 2>&1)"; then
pass "learning coherence"
else
fail "learning coherence"
indent_output "$learning_output"
fi
elif [[ -d .agents/learnings ]]; then
# Inline check: validate frontmatter on changed learnings
learning_errors=0
learn_files="$(find .agents/learnings -name '*.md' -type f 2>/dev/null)"
if [[ "$FAST_MODE" == "true" ]]; then
learn_files="$(echo "$all_changed" | grep '^\.agents/learnings/.*\.md$' || true)"
fi
for f in $learn_files; do
[[ -f "$f" ]] || continue
if ! head -1 "$f" | grep -q '^---'; then
echo " missing frontmatter: $f"
learning_errors=1
fi
done
if [[ "$learning_errors" -eq 0 ]]; then
pass "learning coherence (inline)"
else
fail "learning coherence (missing frontmatter)"
fi
else
skip "learning coherence (no learnings dir)"
fi
else
skip "learning coherence"
fi
# --- 33. BATS tests + orphan hooks ---
if needs_check hook; then
if command -v bats >/dev/null 2>&1 && [[ -d tests/hooks ]]; then
if [[ -x tests/hooks/test-orphan-hooks.sh ]]; then
if orphan_output="$(bash tests/hooks/test-orphan-hooks.sh 2>&1)"; then
pass "orphan hooks audit"
else
fail "orphan hooks audit"
indent_output "$orphan_output"
fi
else
skip "orphan hooks (missing script)"
fi
else
skip "BATS/orphan hooks (bats not installed or no tests/hooks)"
fi
else
skip "orphan hooks"
fi
# --- 34. Skill citation parity ---
if needs_check skill; then
if [[ -x tests/docs/validate-skill-citation-parity.sh ]]; then
if cite_output="$(bash tests/docs/validate-skill-citation-parity.sh 2>&1)"; then
pass "skill citation parity"
else
fail "skill citation parity"
indent_output "$cite_output"
fi
else
skip "skill citation parity (missing script)"
fi
else
skip "skill citation parity"
fi
# --- 35. Flywheel health (warn only) ---
if command -v ao >/dev/null 2>&1 && [[ -d .agents ]]; then
if health_output="$(ao metrics health --json 2>/dev/null)"; then
fly_status="$(echo "$health_output" | grep -o '"flywheel_status":"[^"]*"' | head -1 | cut -d'"' -f4 || true)"
if [[ "$fly_status" == "DECAYING" ]]; then
warn "flywheel health: DECAYING — run /evolve or check citation flow"
elif [[ -n "$fly_status" ]]; then
pass "flywheel health ($fly_status)"
else
skip "flywheel health (no status in output)"
fi
else
skip "flywheel health (ao metrics health failed)"
fi
else
skip "flywheel health (ao not available)"
fi
# --- 36. CHANGELOG sync (docs/CHANGELOG.md must match root) ---
if needs_check docs || needs_check always; then
if [[ -f docs/CHANGELOG.md && -f CHANGELOG.md ]]; then
if diff -q CHANGELOG.md docs/CHANGELOG.md >/dev/null 2>&1; then
pass "CHANGELOG sync"
else
fail "CHANGELOG sync (run: cp CHANGELOG.md docs/CHANGELOG.md)"
fi
else
skip "CHANGELOG sync (missing file)"
fi
fi
# --- 37. ~/.agents content-hash gate (post-hoc mutation detector) ---
# Escape hatch: HASH_GATE_IGNORE_UNTRACKED=1 skips this block entirely so
# operators with local scratch state (docs/blog/, codex_write_test.txt, etc.)
# can push without the gate firing on noise. CI does NOT set this variable,
# so strict enforcement is preserved on main.
if [[ "${HASH_GATE_IGNORE_UNTRACKED:-0}" == "1" ]]; then
skip "agents-hub content-hash gate (HASH_GATE_IGNORE_UNTRACKED=1)"
rm -f "$HASH_GATE_SNAPSHOT" 2>/dev/null || true
elif [[ -n "$HASH_GATE_SNAPSHOT" && -x scripts/check-agents-hash-snapshot.sh ]]; then
if hash_gate_output="$(scripts/check-agents-hash-snapshot.sh diff "$HASH_GATE_SNAPSHOT" 2>&1)"; then
pass "agents-hub content-hash gate"
else
fail "agents-hub mutated during tests (content-hash gate)"
indent_output "$hash_gate_output"
fi
rm -f "$HASH_GATE_SNAPSHOT"
fi
# --- Summary ---
echo ""
if [[ $errors -gt 0 ]]; then
if [[ "$FAST_MODE" == "true" ]]; then
echo -e "${RED}pre-push gate (fast): BLOCKED ($errors failures, $skipped skipped)${NC}"
else
echo -e "${RED}pre-push gate: BLOCKED ($errors failures)${NC}"
fi
exit 1
else
if [[ "$FAST_MODE" == "true" ]]; then
echo -e "${GREEN}pre-push gate (fast): passed ($skipped skipped)${NC}"
else
echo -e "${GREEN}pre-push gate: passed${NC}"
fi
exit 0
fi